| 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 shouldBe500WhenResourceNotFound() 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_INTERNAL_SERVER_ERROR); |
| } |
| |
| @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); |
| } |
| } |