blob: 7b61930f298d28a11c731ead565b278674ba9df2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.ace.deployment.servlet;
import static org.apache.ace.test.utils.TestUtils.configureObject;
import static org.apache.ace.test.utils.TestUtils.createMockObjectAdapter;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.ace.deployment.provider.ArtifactData;
import org.apache.ace.deployment.provider.DeploymentProvider;
import org.apache.ace.deployment.streamgenerator.StreamGenerator;
import org.easymock.IAnswer;
import org.osgi.service.log.LogService;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class DeploymentServletTest {
// the servlet under test
private DeploymentServlet m_servlet;
private long m_artifactSize;
// request state
private HttpServletRequest m_request;
private String m_requestCurrentParameter;
private String m_requestRangeHeader;
private String m_requestPathInfo;
// response state
private HttpServletResponse m_response;
private ByteArrayOutputStream m_responseOutputStream;
private int m_responseStatus;
private Map<String, String> m_responseHeaders;
// deployment provider state
private DeploymentProvider m_provider;
// stream generator state
private StreamGenerator m_generator;
private String m_generatorId;
private String m_generatorFromVersion;
private String m_generatorToVersion;
private InputStream m_generatorResultStream;
@Test
public void getDataForBadURL() throws Exception {
HttpServletRequest garbage = createMockObjectAdapter(HttpServletRequest.class, new Object() {
@SuppressWarnings("unused")
public String getPathInfo() {
return "/";
}
});
m_servlet.doGet(garbage, m_response);
assertResponseCode(HttpServletResponse.SC_BAD_REQUEST);
}
@Test
public void getDataForExistingTarget() throws Exception {
m_requestPathInfo = "/existing/versions/2.0.0";
m_servlet.doGet(m_request, m_response);
assertResponseCode(HttpServletResponse.SC_OK);
assertResponseHeaderNotPresent("Content-Length");
assertResponseOutput(0, 100);
assertGeneratorTargetId("existing");
assertGeneratorToVersion("2.0.0");
}
@Test
public void getDataForNonExistingTarget() throws Exception {
m_requestPathInfo = "/nonexisting/versions/2.0.0";
m_servlet.doGet(m_request, m_response);
assertResponseCode(HttpServletResponse.SC_NOT_FOUND);
}
@Test()
public void getDowngradeFixPackageWithNonExistingToVersion() throws Exception {
// try to request a version range with a non-existing from-version, should cause a complete (non-fix) package to
// be returned...
m_requestPathInfo = "/existing/versions/1.0.0";
m_requestCurrentParameter = "2.0.0";
m_servlet.doGet(m_request, m_response);
assertResponseCode(HttpServletResponse.SC_NOT_FOUND);
}
@Test
public void getRangeDataForExistingTarget_badHeaderValue() throws Exception {
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.16
// If the server ignores a byte-range-spec because it is syntactically invalid, the server SHOULD treat the
// request as if the invalid Range header field did not exist. (Normally, this means return a 200 response
// containing the full entity).
m_requestPathInfo = "/existing/versions/2.0.0";
m_requestRangeHeader = "bytes=a-1";
m_servlet.doGet(m_request, m_response);
assertResponseCode(HttpServletResponse.SC_OK);
assertResponseHeaderNotPresent("Content-Length");
assertResponseOutput(0, 100);
}
@Test
public void getRangeDataForExistingTarget_first0lastOK() throws Exception {
// valid range starting at 0
m_requestPathInfo = "/existing/versions/2.0.0";
m_requestRangeHeader = "bytes=0-10";
m_servlet.doGet(m_request, m_response);
assertResponseCode(HttpServletResponse.SC_PARTIAL_CONTENT);
assertResponseHeaderNotPresent("Content-Length");
assertResponseHeaderValue("Content-Range", "bytes 0-10/*");
}
@Test
public void getRangeDataForExistingTarget_firstOKlastANY() throws Exception {
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
// If the last-byte-pos value is absent, or if the value is greater than or equal to the current length of the
// entity-body, last-byte-pos is taken to be equal to one less than the current length of the entity- body in
// bytes.
m_requestPathInfo = "/existing/versions/2.0.0";
m_requestRangeHeader = "bytes=2-";
m_servlet.doGet(m_request, m_response);
assertResponseCode(HttpServletResponse.SC_PARTIAL_CONTENT);
assertResponseHeaderNotPresent("Content-Length");
assertResponseHeaderValue("Content-Range", "bytes 2-/*");
assertResponseOutput(2, 98);
}
@Test()
public void getRangeDataForExistingTarget_firstOKlastOK() throws Exception {
// valid range not starting at 0
m_requestPathInfo = "/existing/versions/2.0.0";
m_requestRangeHeader = "bytes=2-50";
m_servlet.doGet(m_request, m_response);
assertResponseCode(HttpServletResponse.SC_PARTIAL_CONTENT);
assertResponseHeaderNotPresent("Content-Length");
assertResponseHeaderValue("Content-Range", "bytes 2-50/*");
assertResponseOutput(2, 49);
}
@Test
public void getRangeDataForExistingTarget_firstOKlastOK2() throws Exception {
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
// If the last-byte-pos value is absent, or if the value is greater than or equal to the current length of the
// entity-body, last-byte-pos is taken to be equal to one less than the current length of the entity- body in
// bytes.
m_requestPathInfo = "/existing/versions/2.0.0";
m_requestRangeHeader = "bytes=2-99";
m_servlet.doGet(m_request, m_response);
assertResponseCode(HttpServletResponse.SC_PARTIAL_CONTENT);
assertResponseHeaderNotPresent("Content-Length");
assertResponseHeaderValue("Content-Range", "bytes 2-99/*");
assertResponseOutput(2, 98);
}
@Test
public void getRangeDataForExistingTarget_firstOKlastTooBig() throws Exception {
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
// If the last-byte-pos value is absent, or if the value is greater than or equal to the current length of the
// entity-body, last-byte-pos is taken to be equal to one less than the current length of the entity- body in
// bytes.
m_requestPathInfo = "/existing/versions/2.0.0";
m_requestRangeHeader = "bytes=2-100";
m_servlet.doGet(m_request, m_response);
assertResponseCode(HttpServletResponse.SC_PARTIAL_CONTENT);
assertResponseHeaderNotPresent("Content-Length");
assertResponseHeaderValue("Content-Range", "bytes 2-100/*");
assertResponseOutput(2, 98);
}
@Test
public void getRangeDataForExistingTarget_firstOKlastTooSmall() throws Exception {
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
// If the last-byte-pos value is present, it MUST be greater than or equal to the first-byte-pos in that
// byte-range-spec, or the byte- range-spec is syntactically invalid.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.16
// If the server ignores a byte-range-spec because it is syntactically invalid, the server SHOULD treat the
// request as if the invalid Range header field did not exist. (Normally, this means return a 200 response
// containing the full entity).
m_requestPathInfo = "/existing/versions/2.0.0";
m_requestRangeHeader = "bytes=2-1";
m_servlet.doGet(m_request, m_response);
assertResponseCode(HttpServletResponse.SC_OK);
assertResponseHeaderNotPresent("Content-Length");
assertResponseOutput(0, 100);
}
@Test
public void getRangeDataForExistingTarget_firstTooBiglastTooBig() throws Exception {
// invalid range: start=toobig end=toobig
m_requestPathInfo = "/existing/versions/2.0.0";
m_requestRangeHeader = "bytes=100-110";
m_servlet.doGet(m_request, m_response);
assertResponseCode(HttpServletResponse.SC_PARTIAL_CONTENT);
assertResponseHeaderValue("Content-Range", "bytes 100-110/*");
assertResponseHeaderNotPresent("Content-Length");
assertResponseOutput(-1, 0);
}
@Test
public void getSizeForExistingTargetWithKnownSize() throws Exception {
m_artifactSize = 10;
m_requestPathInfo = "/existing/versions/2.0.0";
m_servlet.doHead(m_request, m_response);
assertResponseCode(HttpServletResponse.SC_OK);
assertResponseHeaderValue("X-ACE-DPSize", "11"); // 10 + 10%
}
@Test
public void getSizeForExistingTargetWithUnknownSize() throws Exception {
m_artifactSize = -1;
m_requestPathInfo = "/existing/versions/2.0.0";
m_servlet.doHead(m_request, m_response);
assertResponseCode(HttpServletResponse.SC_OK);
assertResponseHeaderNotPresent("X-ACE-DPSize");
}
@Test
public void getSizeForFixPackageExistingTargetWithKnownSize() throws Exception {
m_artifactSize = 10;
m_requestCurrentParameter = "2.0.0";
m_requestPathInfo = "/existing/versions/2.0.0";
m_servlet.doHead(m_request, m_response);
assertResponseCode(HttpServletResponse.SC_OK);
assertResponseHeaderValue("X-ACE-DPSize", "22"); // 20 + 10%
}
@Test
public void getSizeForNonExistingTarget() throws Exception {
m_artifactSize = 10;
m_requestPathInfo = "/existing/versions/1.0.0";
m_servlet.doHead(m_request, m_response);
assertResponseCode(HttpServletResponse.SC_OK);
assertResponseHeaderNotPresent("X-ACE-DPSize");
}
@Test()
public void getUpgradeFixPackageWithExistingFromVersion() throws Exception {
// try to request a version range with an existing from-version, should cause a fix package to be returned...
m_requestPathInfo = "/existing/versions/2.0.0";
m_requestCurrentParameter = "2.0.0";
m_servlet.doGet(m_request, m_response);
assertResponseCode(HttpServletResponse.SC_OK);
assertResponseOutput(0, 100);
assertGeneratorTargetId("existing");
assertGeneratorToVersion("2.0.0");
assertGeneratorFromVersion("2.0.0");
}
@Test()
public void getUpgradeFixPackageWithNonExistingFromVersion() throws Exception {
// try to request a version range with a non-existing from-version, should cause a complete (non-fix) package to
// be returned...
m_requestPathInfo = "/existing/versions/2.0.0";
m_requestCurrentParameter = "1.0.0";
m_servlet.doGet(m_request, m_response);
assertResponseCode(HttpServletResponse.SC_OK);
assertResponseOutput(0, 100);
assertGeneratorTargetId("existing");
assertGeneratorToVersion("2.0.0");
assertGeneratorFromVersion(null);
}
@Test()
public void getUpgradeFixPackageWithNonExistingToVersion() throws Exception {
// try to request a version range with a non-existing from-version, should cause a complete (non-fix) package to
// be returned...
m_requestPathInfo = "/existing/versions/3.0.0";
m_requestCurrentParameter = "2.0.0";
m_servlet.doGet(m_request, m_response);
assertResponseCode(HttpServletResponse.SC_NOT_FOUND);
}
@Test()
public void getUpgradeWithExistingToVersion() throws Exception {
// try to request a version range with a non-existing from-version, should cause a complete (non-fix) package to
// be returned...
m_requestPathInfo = "/existing/versions/2.0.0";
m_requestCurrentParameter = null;
m_servlet.doGet(m_request, m_response);
assertResponseCode(HttpServletResponse.SC_OK);
assertResponseOutput(0, 100);
assertGeneratorTargetId("existing");
assertGeneratorToVersion("2.0.0");
assertGeneratorFromVersion(null);
}
@Test()
public void getUpgradeWithNonExistingToVersion() throws Exception {
// try to request a version range with a non-existing from-version, should cause a complete (non-fix) package to
// be returned...
m_requestPathInfo = "/existing/versions/3.0.0";
m_requestCurrentParameter = null;
m_servlet.doGet(m_request, m_response);
assertResponseCode(HttpServletResponse.SC_NOT_FOUND);
}
@Test
public void getVersionsExistingTarget() throws Exception {
m_requestPathInfo = "/existing/versions";
m_servlet.doGet(m_request, m_response);
assertResponseCode(HttpServletResponse.SC_OK);
assertEquals(m_responseOutputStream.toString(), "2.0.0\n", "Expected to get version 2.0.0 in the response");
}
@Test
public void getVersionsNonExistingTarget() throws Exception {
m_requestPathInfo = "/nonexisting/versions";
m_servlet.doGet(m_request, m_response);
assertResponseCode(HttpServletResponse.SC_NOT_FOUND);
assertResponseOutput(-1, 0);
}
@BeforeMethod(alwaysRun = true)
protected void setUp() throws Exception {
final Map<String, List<String>> providerVersions = new HashMap<>();
providerVersions.put("existing", Arrays.asList("2.0.0"));
final ArtifactData artifactData = createMock(ArtifactData.class);
expect(artifactData.getSize()).andAnswer(new IAnswer<Long>() {
@Override
public Long answer() throws Throwable {
return DeploymentServletTest.this.m_artifactSize;
}
}).anyTimes();
replay(artifactData);
m_provider = new DeploymentProvider() {
public List<ArtifactData> getBundleData(String targetId, String version) throws IllegalArgumentException {
List<String> versions = providerVersions.get(targetId);
if (versions != null && versions.contains(version)) {
return Arrays.asList(artifactData);
}
return Collections.emptyList();
}
public List<ArtifactData> getBundleData(String targetId, String versionFrom, String versionTo) throws IllegalArgumentException {
List<String> versions = providerVersions.get(targetId);
if (versions != null && versions.contains(versionFrom) && versions.contains(versionTo)) {
return Arrays.asList(artifactData, artifactData);
}
return Collections.emptyList();
}
public List<String> getVersions(String targetId) throws IllegalArgumentException {
if (providerVersions.containsKey(targetId)) {
return providerVersions.get(targetId);
}
throw new IllegalArgumentException();
}
};
m_generator = new StreamGenerator() {
public InputStream getDeploymentPackage(String id, String version) throws IOException {
if (m_generatorResultStream == null) {
throw new IOException("No data for " + id + " " + version);
}
m_generatorId = id;
m_generatorToVersion = version;
return m_generatorResultStream;
}
public InputStream getDeploymentPackage(String id, String fromVersion, String toVersion) throws IOException {
if (m_generatorResultStream == null) {
throw new IOException("No delta for " + id + " " + fromVersion + " " + toVersion);
}
m_generatorId = id;
m_generatorFromVersion = fromVersion;
m_generatorToVersion = toVersion;
return m_generatorResultStream;
}
};
// create a HttpServletRequest mock object
m_request = createMockObjectAdapter(HttpServletRequest.class, new Object() {
@SuppressWarnings("unused")
public String getHeader(String name) {
if (name.equals("Range")) {
return m_requestRangeHeader;
}
return null;
}
@SuppressWarnings("unused")
public String getParameter(String param) {
if (param.equals(DeploymentServlet.CURRENT)) {
return m_requestCurrentParameter;
}
return null;
}
@SuppressWarnings("unused")
public String getPathInfo() {
return m_requestPathInfo;
}
});
// create a HttpServletResponse mock object
m_response = createMockObjectAdapter(HttpServletResponse.class, new Object() {
@SuppressWarnings("unused")
public void addHeader(String name, String value) {
m_responseHeaders.put(name, value);
}
@SuppressWarnings("unused")
public ServletOutputStream getOutputStream() {
return new ServletOutputStream() {
@Override
public void write(int b) throws IOException {
m_responseOutputStream.write(b);
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setWriteListener(WriteListener l) {
// nop
}
};
}
public synchronized void sendError(int status) {
m_responseStatus = status;
}
@SuppressWarnings("unused")
public void sendError(int status, String desc) {
sendError(status);
}
@SuppressWarnings("unused")
public void setHeader(String name, String value) {
m_responseHeaders.put(name, value);
}
public void setStatus(int status) {
m_responseStatus = status;
}
@SuppressWarnings("unused")
public void setStatus(int status, String desc) {
setStatus(status);
}
});
// create the instance to test
m_servlet = new DeploymentServlet();
configureObject(m_servlet, LogService.class);
configureObject(m_servlet, StreamGenerator.class, m_generator);
configureObject(m_servlet, DeploymentProvider.class, m_provider);
byte[] data = new byte[100];
for (int i = 0; i < data.length; i++) {
data[i] = (byte) (i + 1);
}
// set the default state
m_generatorResultStream = new ByteArrayInputStream(data);
m_requestCurrentParameter = null;
m_generatorId = null;
m_generatorFromVersion = null;
m_generatorToVersion = null;
m_responseStatus = HttpServletResponse.SC_OK;
m_responseHeaders = new HashMap<>();
m_requestRangeHeader = null;
m_responseOutputStream = new ByteArrayOutputStream();
}
private void assertGeneratorFromVersion(String version) {
assertEquals(m_generatorFromVersion, version, "Wrong from-version");
}
private void assertGeneratorTargetId(String id) {
assertEquals(m_generatorId, id, "Wrong target ID");
}
private void assertGeneratorToVersion(String version) {
assertEquals(m_generatorToVersion, version, "Wrong to-version");
}
private void assertResponseCode(int value) throws Exception {
assertEquals(m_responseStatus, value, "Incorrect response code from server");
}
private void assertResponseHeaderNotPresent(String name) throws Exception {
assertFalse(m_responseHeaders.containsKey(name), "Expected response " + name + " header to NOT be set");
}
private void assertResponseHeaderValue(String name, String value) throws Exception {
assertTrue(m_responseHeaders.containsKey(name), "Expected response " + name + " header to be set");
assertEquals(m_responseHeaders.get(name), value, "Unexpected response header");
}
private void assertResponseOutput(int offset, int size) throws Exception {
byte[] data = m_responseOutputStream.toByteArray();
assertEquals(data.length, size, "We should have got a (dummy) deployment package of");
for (int i = 0; i < data.length; i++) {
assertEquals(data[i], i + offset + 1);
}
}
}