diff --git a/autograde-service/src/test/java/ch/epfl/autograde/controller/api/v1/SubmissionControllerIntegrationTests.java b/autograde-service/src/test/java/ch/epfl/autograde/controller/api/v1/SubmissionControllerIntegrationTests.java new file mode 100644 index 0000000000000000000000000000000000000000..61afc2f4a4af72501e2eefe55f22bec7ce0bd562 --- /dev/null +++ b/autograde-service/src/test/java/ch/epfl/autograde/controller/api/v1/SubmissionControllerIntegrationTests.java @@ -0,0 +1,183 @@ +package ch.epfl.autograde.controller.api.v1; + +import ch.epfl.autograde.filters.AssignRequestIdFilter; +import ch.epfl.autograde.utils.context.WithSharedSecretAuthentication; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpHeaders; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.Random; + +import static java.lang.String.format; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; + +/** + * Test class for the {@link SubmissionController} API. + * <p> + * This class verifies the behaviour of the submissions endpoint provided by the API: + * <ul> + * <li>{@code POST:/api/v1/submission}</li> + * <li>{@code GET:/api/v1/submission/{id}}</li> + * <li>{@code GET:/api/v1/submission/{id}/status}</li> + * <li>{@code GET:/api/v1/submission/{id}/files}</li> + * <li>{@code POST:/api/v1/submission/{id}/feedback}</li> + * </ul> + * It uses Spring Boot's {@link MockMvc} for testing HTTP requests and responses, + * and the {@link WithSharedSecretAuthentication} annotation to simulate requests + * with <i>shared secret</i> authentication. + * + * @see ch.epfl.autograde.controller.api.v1.SubmissionController + * @see ch.epfl.autograde.config.SecurityConfig + * + * @author Hamza REMMAL (hamza.remmal@epfl.ch) + */ +@SpringBootTest +@AutoConfigureMockMvc +@DisplayName("[Integration Tests] '/api/v1/submission/**'") +final class SubmissionControllerIntegrationTests { + + private final Random rnd = new Random(); + @Autowired private MockMvc mockMvc; + + // ============================================================================================== + // ================================== POST:/api/v1/submission =================================== + // ============================================================================================== + + /** + * If <b>not</b> authenticated with the shared secret, the request to {@code POST:/api/v1/submission} + * should <b>fail</b> + * <p> + * The following test will verify that: + * <ul> + * <li>The HTTP Status is {@link org.springframework.http.HttpStatus.UNAUTHORIZED}</li> + * <li>The request will not generate cookies</li> + * <li>A `X-Request-Id` header was set in the response</li> + * </ul> + */ + @Test + @DisplayName("'POST:/api/v1/submission' returns 401 when unauthenticated") + void createSubmissionShouldBeAuthenticated() throws Exception { + mockMvc.perform(post("/api/v1/submission")) + .andExpectAll( + status().isUnauthorized(), + header().doesNotExist(HttpHeaders.SET_COOKIE), + header().exists(AssignRequestIdFilter.REQUEST_ID_HEADER) + ); + } + + // ============================================================================================== + // ================================ GET:/api/v1/submission/{id} ================================= + // ============================================================================================== + + /** + * If <b>not</b> authenticated with the shared secret, the request to {@code GET:/api/v1/submission/{id}} + * should <b>fail</b> + * <p> + * The following test will verify that: + * <ul> + * <li>The HTTP Status is {@link org.springframework.http.HttpStatus.UNAUTHORIZED}</li> + * <li>The request will not generate cookies</li> + * <li>A `X-Request-Id` header was set in the response</li> + * </ul> + */ + @Test + @DisplayName("'GET:/api/v1/submission/{id}' returns 401 when unauthenticated") + void fetchingSubmissionShouldBeAuthenticated() throws Exception { + final var id = rnd.nextInt(); + mockMvc.perform(get(format("/api/v1/submission/%d", id))) + .andExpectAll( + status().isUnauthorized(), + header().doesNotExist(HttpHeaders.SET_COOKIE), + header().exists(AssignRequestIdFilter.REQUEST_ID_HEADER) + ); + } + + // ============================================================================================== + // ============================= GET:/api/v1/submission/{id}/status ============================= + // ============================================================================================== + + /** + * If <b>not</b> authenticated with the shared secret, the request to {@code GET:/api/v1/submission/{id}/status} + * should <b>fail</b> + * <p> + * The following test will verify that: + * <ul> + * <li>The HTTP Status is {@link org.springframework.http.HttpStatus.UNAUTHORIZED}</li> + * <li>The request will not generate cookies</li> + * <li>A `X-Request-Id` header was set in the response</li> + * </ul> + */ + @Test + @DisplayName("'GET:/api/v1/submission/{id}/status' returns 401 when unauthenticated") + void statusShouldBeAuthenticated() throws Exception { + final var id = rnd.nextInt(); + mockMvc.perform(get(format("/api/v1/submission/%d/status", id))) + .andExpectAll( + status().isUnauthorized(), + header().doesNotExist(HttpHeaders.SET_COOKIE), + header().exists(AssignRequestIdFilter.REQUEST_ID_HEADER) + ); + } + + // ============================================================================================== + // ============================= GET:/api/v1/submission/{id}/files ============================== + // ============================================================================================== + + /** + * If <b>not</b> authenticated with the shared secret, the request to {@code GET:/api/v1/submission/{id}/files} + * should <b>fail</b> + * <p> + * The following test will verify that: + * <ul> + * <li>The HTTP Status is {@link org.springframework.http.HttpStatus.UNAUTHORIZED}</li> + * <li>The request will not generate cookies</li> + * <li>A `X-Request-Id` header was set in the response</li> + * </ul> + */ + @Test + @DisplayName("'GET:/api/v1/submission/{id}/files' returns 401 when unauthenticated") + void submissionFilesShouldBeAuthenticated() throws Exception { + final var id = rnd.nextInt(); + mockMvc.perform(get(format("/api/v1/submission/%d/files", id))) + .andExpectAll( + status().isUnauthorized(), + header().doesNotExist(HttpHeaders.SET_COOKIE), + header().exists(AssignRequestIdFilter.REQUEST_ID_HEADER) + ); + } + + // ============================================================================================== + // ============================ POST:/api/v1/submission/{id}/feedback =========================== + // ============================================================================================== + + /** + * If <b>not</b> authenticated with the shared secret, the request to {@code POST:/api/v1/submission/{id}/feedback} + * should <b>fail</b> + * <p> + * The following test will verify that: + * <ul> + * <li>The HTTP Status is {@link org.springframework.http.HttpStatus.UNAUTHORIZED}</li> + * <li>The request will not generate cookies</li> + * <li>A `X-Request-Id` header was set in the response</li> + * </ul> + */ + @Test + @DisplayName("'POST:/api/v1/submission/{id}/feedback' returns 401 when unauthenticated") + void uploadFeedbackShouldBeAuthenticated() throws Exception { + final var id = rnd.nextInt(); + mockMvc.perform(post(format("/api/v1/submission/%d/feedback", id))) + .andExpectAll( + status().isUnauthorized(), + header().doesNotExist(HttpHeaders.SET_COOKIE), + header().exists(AssignRequestIdFilter.REQUEST_ID_HEADER) + ); + } + +}