blob: bdad210d7cd0596eec924d000f37f2d612e393ec [file] [log] [blame]
package com.googlesource.gerrit.plugins.replication.pull.api;
import static com.google.common.net.HttpHeaders.ACCEPT;
import static com.google.gerrit.httpd.restapi.RestApiServlet.SC_UNPROCESSABLE_ENTITY;
import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.internal.verification.VerificationModeFactory.atLeastOnce;
import static org.mockito.internal.verification.VerificationModeFactory.times;
import com.google.common.net.MediaType;
import com.google.gerrit.extensions.restapi.*;
import com.google.gerrit.server.project.ProjectResource;
import com.google.gerrit.server.restapi.project.ProjectsCollection;
import java.io.*;
import java.nio.charset.StandardCharsets;
import javax.servlet.FilterChain;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class PullReplicationFilterTest {
@Mock HttpServletRequest request;
@Mock HttpServletResponse response;
@Mock FilterChain filterChain;
@Mock private FetchAction fetchAction;
@Mock private ApplyObjectAction applyObjectAction;
@Mock private ApplyObjectsAction applyObjectsAction;
@Mock private ProjectInitializationAction projectInitializationAction;
@Mock private UpdateHeadAction updateHEADAction;
@Mock private ProjectDeletionAction projectDeletionAction;
@Mock private ProjectsCollection projectsCollection;
@Mock private ProjectResource projectResource;
@Mock private ServletOutputStream outputStream;
@Mock private PrintWriter printWriter;
private final String PLUGIN_NAME = "pull-replication";
private final String PROJECT_NAME = "some-project";
private final String PROJECT_NAME_GIT = "some-project.git";
private final String FETCH_URI =
String.format("any-prefix/projects/%s/%s~fetch", PROJECT_NAME, PLUGIN_NAME);
private final String APPLY_OBJECT_URI =
String.format("any-prefix/projects/%s/%s~apply-object", PROJECT_NAME, PLUGIN_NAME);
private final String APPLY_OBJECTS_URI =
String.format("any-prefix/projects/%s/%s~apply-objects", PROJECT_NAME, PLUGIN_NAME);
private final String HEAD_URI = String.format("any-prefix/projects/%s/HEAD", PROJECT_NAME);
private final String DELETE_PROJECT_URI =
String.format("any-prefix/projects/%s/%s~delete-project", PROJECT_NAME, PLUGIN_NAME);
private final String INIT_PROJECT_URI =
String.format("any-prefix/%s/init-project/%s", PLUGIN_NAME, PROJECT_NAME_GIT);
private final Response OK_RESPONSE = Response.ok();
private PullReplicationFilter createPullReplicationFilter() {
return new PullReplicationFilter(
fetchAction,
applyObjectAction,
applyObjectsAction,
projectInitializationAction,
updateHEADAction,
projectDeletionAction,
projectsCollection,
PLUGIN_NAME);
}
private void defineBehaviours(byte[] payload, String uri) throws Exception {
when(request.getRequestURI()).thenReturn(uri);
InputStream is = new ByteArrayInputStream(payload);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
when(request.getReader()).thenReturn(bufferedReader);
when(projectsCollection.parse(TopLevelResource.INSTANCE, IdString.fromDecoded(PROJECT_NAME)))
.thenReturn(projectResource);
when(response.getWriter()).thenReturn(printWriter);
}
private void verifyBehaviours() throws Exception {
verify(request, atLeastOnce()).getRequestURI();
verify(request).getReader();
verify(projectsCollection).parse(TopLevelResource.INSTANCE, IdString.fromDecoded(PROJECT_NAME));
verify(response).getWriter();
verify(response).setContentType("application/json");
verify(response).setStatus(HttpServletResponse.SC_OK);
}
@Test
public void shouldFilterFetchAction() throws Exception {
byte[] payloadFetch =
("{"
+ "\"label\":\"Replication\", "
+ "\"ref_name\": \"refs/heads/master\", "
+ "\"async\":false"
+ "}")
.getBytes(StandardCharsets.UTF_8);
defineBehaviours(payloadFetch, FETCH_URI);
when(fetchAction.apply(any(), any())).thenReturn(OK_RESPONSE);
PullReplicationFilter pullReplicationFilter = createPullReplicationFilter();
pullReplicationFilter.doFilter(request, response, filterChain);
verifyBehaviours();
verify(fetchAction).apply(eq(projectResource), any());
}
@Test
public void shouldFilterApplyObjectAction() throws Exception {
byte[] payloadApplyObject =
("{\"label\":\"Replication\",\"ref_name\":\"refs/heads/master\","
+ "\"revision_data\":{"
+ "\"commit_object\":{\"type\":1,\"content\":\"some-content\"},"
+ "\"tree_object\":{\"type\":2,\"content\":\"some-content\"},"
+ "\"blobs\":[]}"
+ "}")
.getBytes(StandardCharsets.UTF_8);
defineBehaviours(payloadApplyObject, APPLY_OBJECT_URI);
when(applyObjectAction.apply(any(), any())).thenReturn(OK_RESPONSE);
PullReplicationFilter pullReplicationFilter = createPullReplicationFilter();
pullReplicationFilter.doFilter(request, response, filterChain);
verifyBehaviours();
verify(applyObjectAction).apply(eq(projectResource), any());
}
@Test
public void shouldFilterApplyObjectsAction() throws Exception {
byte[] payloadApplyObjects =
("{\"label\":\"Replication\",\"ref_name\":\"refs/heads/master\","
+ "\"revisions_data\":[{"
+ "\"commit_object\":{\"type\":1,\"content\":\"some-content\"},"
+ "\"tree_object\":{\"type\":2,\"content\":\"some-content\"},"
+ "\"blobs\":[]}]}")
.getBytes(StandardCharsets.UTF_8);
defineBehaviours(payloadApplyObjects, APPLY_OBJECTS_URI);
when(applyObjectsAction.apply(any(), any())).thenReturn(OK_RESPONSE);
PullReplicationFilter pullReplicationFilter = createPullReplicationFilter();
pullReplicationFilter.doFilter(request, response, filterChain);
verifyBehaviours();
verify(applyObjectsAction).apply(eq(projectResource), any());
}
@Test
public void shouldFilterProjectInitializationAction() throws Exception {
when(request.getRequestURI()).thenReturn(INIT_PROJECT_URI);
when(request.getHeader(ACCEPT)).thenReturn(MediaType.PLAIN_TEXT_UTF_8.toString());
when(projectInitializationAction.initProject(PROJECT_NAME_GIT)).thenReturn(true);
when(response.getWriter()).thenReturn(printWriter);
final PullReplicationFilter pullReplicationFilter = createPullReplicationFilter();
pullReplicationFilter.doFilter(request, response, filterChain);
verify(request, times(5)).getRequestURI();
verify(projectInitializationAction).initProject(eq(PROJECT_NAME_GIT));
verify(response).getWriter();
}
@Test
public void shouldFilterUpdateHEADAction() throws Exception {
byte[] payloadUpdateHead = "{\"ref\":\"some-ref\"}".getBytes(StandardCharsets.UTF_8);
defineBehaviours(payloadUpdateHead, HEAD_URI);
when(request.getMethod()).thenReturn("PUT");
when(updateHEADAction.apply(any(), any())).thenReturn(OK_RESPONSE);
final PullReplicationFilter pullReplicationFilter = createPullReplicationFilter();
pullReplicationFilter.doFilter(request, response, filterChain);
verifyBehaviours();
verify(updateHEADAction).apply(eq(projectResource), any());
}
@Test
public void shouldFilterProjectDeletionAction() throws Exception {
when(request.getRequestURI()).thenReturn(DELETE_PROJECT_URI);
when(request.getMethod()).thenReturn("DELETE");
when(projectsCollection.parse(TopLevelResource.INSTANCE, IdString.fromDecoded(PROJECT_NAME)))
.thenReturn(projectResource);
when(projectDeletionAction.apply(any(), any())).thenReturn(OK_RESPONSE);
when(response.getWriter()).thenReturn(printWriter);
final PullReplicationFilter pullReplicationFilter = createPullReplicationFilter();
pullReplicationFilter.doFilter(request, response, filterChain);
verify(request, times(7)).getRequestURI();
verify(projectsCollection).parse(TopLevelResource.INSTANCE, IdString.fromDecoded(PROJECT_NAME));
verify(projectDeletionAction).apply(eq(projectResource), any());
verify(response).getWriter();
verify(response).setContentType("application/json");
verify(response).setStatus(OK_RESPONSE.statusCode());
}
@Test
public void shouldGoNextInChainWhenUriDoesNotMatch() throws Exception {
when(request.getRequestURI()).thenReturn("any-url");
final PullReplicationFilter pullReplicationFilter = createPullReplicationFilter();
pullReplicationFilter.doFilter(request, response, filterChain);
verify(filterChain).doFilter(request, response);
}
@Test
public void shouldBe404WhenJsonIsMalformed() throws Exception {
byte[] payloadMalformedJson = "some-json-malformed".getBytes(StandardCharsets.UTF_8);
InputStream is = new ByteArrayInputStream(payloadMalformedJson);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
when(request.getRequestURI()).thenReturn(FETCH_URI);
when(request.getReader()).thenReturn(bufferedReader);
when(response.getOutputStream()).thenReturn(outputStream);
PullReplicationFilter pullReplicationFilter = createPullReplicationFilter();
pullReplicationFilter.doFilter(request, response, filterChain);
verify(response).setStatus(HttpServletResponse.SC_BAD_REQUEST);
}
@Test
public void shouldBe500WhenProjectCannotBeInitiated() throws Exception {
when(request.getRequestURI()).thenReturn(INIT_PROJECT_URI);
when(request.getHeader(ACCEPT)).thenReturn(MediaType.PLAIN_TEXT_UTF_8.toString());
when(projectInitializationAction.initProject(PROJECT_NAME_GIT)).thenReturn(false);
when(response.getOutputStream()).thenReturn(outputStream);
final PullReplicationFilter pullReplicationFilter = createPullReplicationFilter();
pullReplicationFilter.doFilter(request, response, filterChain);
verify(response).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
@Test
public void shouldBe404WhenResourceNotFound() throws Exception {
when(request.getRequestURI()).thenReturn(DELETE_PROJECT_URI);
when(request.getMethod()).thenReturn("DELETE");
when(projectsCollection.parse(TopLevelResource.INSTANCE, IdString.fromDecoded(PROJECT_NAME)))
.thenReturn(projectResource);
when(projectDeletionAction.apply(any(), any()))
.thenThrow(new ResourceNotFoundException("resource not found"));
when(response.getOutputStream()).thenReturn(outputStream);
final PullReplicationFilter pullReplicationFilter = createPullReplicationFilter();
pullReplicationFilter.doFilter(request, response, filterChain);
verify(response).setStatus(HttpServletResponse.SC_NOT_FOUND);
}
@Test
public void shouldBe403WhenUserIsNotAuthorised() throws Exception {
byte[] payloadFetchAction =
("{"
+ "\"label\":\"Replication\", "
+ "\"ref_name\": \"refs/heads/master\", "
+ "\"async\":false"
+ "}")
.getBytes(StandardCharsets.UTF_8);
defineBehaviours(payloadFetchAction, FETCH_URI);
when(fetchAction.apply(any(), any()))
.thenThrow(new AuthException("The user is not authorised"));
when(response.getOutputStream()).thenReturn(outputStream);
PullReplicationFilter pullReplicationFilter = createPullReplicationFilter();
pullReplicationFilter.doFilter(request, response, filterChain);
verify(response).setStatus(HttpServletResponse.SC_FORBIDDEN);
}
@Test
public void shouldBe422WhenEntityCannotBeProcessed() throws Exception {
byte[] payloadFetchAction =
("{"
+ "\"label\":\"Replication\", "
+ "\"ref_name\": \"refs/heads/master\", "
+ "\"async\":false"
+ "}")
.getBytes(StandardCharsets.UTF_8);
defineBehaviours(payloadFetchAction, FETCH_URI);
when(fetchAction.apply(any(), any()))
.thenThrow(new UnprocessableEntityException("Entity cannot be processed"));
when(response.getOutputStream()).thenReturn(outputStream);
PullReplicationFilter pullReplicationFilter = createPullReplicationFilter();
pullReplicationFilter.doFilter(request, response, filterChain);
verify(response).setStatus(SC_UNPROCESSABLE_ENTITY);
}
@Test
public void shouldBe409WhenThereIsResourceConflict() throws Exception {
when(request.getRequestURI()).thenReturn(DELETE_PROJECT_URI);
when(request.getMethod()).thenReturn("DELETE");
when(projectsCollection.parse(TopLevelResource.INSTANCE, IdString.fromDecoded(PROJECT_NAME)))
.thenReturn(projectResource);
when(projectDeletionAction.apply(any(), any()))
.thenThrow(new ResourceConflictException("Resource conflict"));
when(response.getOutputStream()).thenReturn(outputStream);
PullReplicationFilter pullReplicationFilter = createPullReplicationFilter();
pullReplicationFilter.doFilter(request, response, filterChain);
verify(response).setStatus(SC_CONFLICT);
}
@Test
public void shouldBe400WhenProjectNameIsNotPresentInURL() throws Exception {
when(request.getRequestURI())
.thenReturn(String.format("any-prefix/projects/%s~delete-project", PLUGIN_NAME));
when(request.getMethod()).thenReturn("DELETE");
when(response.getOutputStream()).thenReturn(outputStream);
PullReplicationFilter pullReplicationFilter = createPullReplicationFilter();
pullReplicationFilter.doFilter(request, response, filterChain);
verify(response).setStatus(HttpServletResponse.SC_BAD_REQUEST);
}
}