From e8f8a5ee7c9f1e464b243d1fa338f6dacf70fada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Costa?= Date: Sat, 22 Jul 2023 15:58:11 +0100 Subject: [PATCH 1/9] Refactor accounts endpoints --- .../backend/controller/AccountController.kt | 4 +-- .../controller/AccountControllerTest.kt | 30 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/AccountController.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/AccountController.kt index d2d9acbd..00819298 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/AccountController.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/AccountController.kt @@ -29,7 +29,7 @@ class AccountController(private val service: AccountService) { @GetMapping("/{id}") fun getAccountById(@PathVariable id: Long) = service.getAccountById(id) - @PostMapping("/new", consumes = ["multipart/form-data"]) + @PostMapping(consumes = ["multipart/form-data"]) fun createAccount( @RequestPart account: CreateAccountDto, @RequestParam @@ -58,7 +58,7 @@ class AccountController(private val service: AccountService) { return emptyMap() } - @PostMapping("/changePassword/{id}") + @PostMapping("/{id}/password") fun changePassword(@PathVariable id: Long, @RequestBody dto: ChangePasswordDto): Map { service.changePassword(id, dto) return emptyMap() diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AccountControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AccountControllerTest.kt index 04ec2f12..e7c776f0 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AccountControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AccountControllerTest.kt @@ -1,5 +1,6 @@ package pt.up.fe.ni.website.backend.controller +import pt.up.fe.ni.website.backend.model.constants.AccountConstants as Constants import com.epages.restdocs.apispec.ResourceDocumentation.parameterWithName import com.fasterxml.jackson.databind.ObjectMapper import java.util.Calendar @@ -29,7 +30,6 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status import pt.up.fe.ni.website.backend.config.upload.UploadConfigProperties import pt.up.fe.ni.website.backend.model.Account import pt.up.fe.ni.website.backend.model.CustomWebsite -import pt.up.fe.ni.website.backend.model.constants.AccountConstants as Constants import pt.up.fe.ni.website.backend.repository.AccountRepository import pt.up.fe.ni.website.backend.utils.TestUtils import pt.up.fe.ni.website.backend.utils.ValidationTester @@ -167,7 +167,7 @@ class AccountControllerTest @Autowired constructor( } @NestedTest - @DisplayName("POST /accounts/new") + @DisplayName("POST /accounts") inner class CreateAccount { private val uuid: UUID = UUID.randomUUID() private val mockedSettings = Mockito.mockStatic(UUID::class.java) @@ -184,7 +184,7 @@ class AccountControllerTest @Autowired constructor( @Test fun `should create the account`() { - mockMvc.multipartBuilder("/accounts/new") + mockMvc.multipartBuilder("/accounts") .addPart("account", testAccount.toJson()) .perform() .andExpectAll( @@ -228,7 +228,7 @@ class AccountControllerTest @Autowired constructor( ) ) - mockMvc.multipartBuilder("/accounts/new") + mockMvc.multipartBuilder("/accounts") .addPart("account", data) .perform() .andExpectAll( @@ -248,7 +248,7 @@ class AccountControllerTest @Autowired constructor( fun `should create the account with valid image`() { val expectedPhotoPath = "${uploadConfigProperties.staticServe}/profile/${testAccount.email}-$uuid.jpeg" - mockMvc.multipartBuilder("/accounts/new") + mockMvc.multipartBuilder("/accounts") .addPart("account", testAccount.toJson()) .addFile() .perform() @@ -276,7 +276,7 @@ class AccountControllerTest @Autowired constructor( @Test fun `should fail to create account with invalid filename extension`() { - mockMvc.multipartBuilder("/accounts/new") + mockMvc.multipartBuilder("/accounts") .addPart("account", testAccount.toJson()) .addFile(filename = "photo.pdf") .perform() @@ -292,7 +292,7 @@ class AccountControllerTest @Autowired constructor( @Test fun `should fail to create account with invalid filename media type`() { - mockMvc.multipartBuilder("/accounts/new") + mockMvc.multipartBuilder("/accounts") .addPart("account", testAccount.toJson()) .addFile(contentType = MediaType.APPLICATION_PDF_VALUE) .perform() @@ -307,7 +307,7 @@ class AccountControllerTest @Autowired constructor( @Test fun `should fail when missing account part`() { - mockMvc.multipartBuilder("/accounts/new") + mockMvc.multipartBuilder("/accounts") .perform() .andExpectAll( status().isBadRequest, @@ -323,7 +323,7 @@ class AccountControllerTest @Autowired constructor( inner class InputValidation { private val validationTester = ValidationTester( req = { params: Map -> - mockMvc.multipartBuilder("/accounts/new") + mockMvc.multipartBuilder("/accounts") .addPart("account", objectMapper.writeValueAsString(params)) .perform() .andDocumentErrorResponse(documentation, hasRequestPayload = true) @@ -463,7 +463,7 @@ class AccountControllerTest @Autowired constructor( ) accountPart.headers.contentType = MediaType.APPLICATION_JSON - mockMvc.perform(multipart("/accounts/new").part(accountPart)) + mockMvc.perform(multipart("/accounts").part(accountPart)) .andDocumentErrorResponse(documentation, hasRequestPayload = true) }, requiredFields = mapOf( @@ -538,12 +538,12 @@ class AccountControllerTest @Autowired constructor( @Test fun `should fail to create account with existing email`() { - mockMvc.multipartBuilder("/accounts/new") + mockMvc.multipartBuilder("/accounts") .addPart("account", testAccount.toJson()) .perform() .andExpect(status().isOk) - mockMvc.multipartBuilder("/accounts/new") + mockMvc.multipartBuilder("/accounts") .addPart("account", testAccount.toJson()) .perform() .andExpectAll( @@ -556,7 +556,7 @@ class AccountControllerTest @Autowired constructor( } @NestedTest - @DisplayName("POST /accounts/changePassword/{id}") + @DisplayName("POST /accounts/{id}/password") inner class ChangePassword { private val password = "test_password" private val changePasswordAccount: Account = ObjectMapper().readValue( @@ -590,7 +590,7 @@ class AccountControllerTest @Autowired constructor( @Test fun `should change password`() { mockMvc.perform( - post("/accounts/changePassword/{id}", changePasswordAccount.id) + post("/accounts/{id}/password", changePasswordAccount.id) .contentType(MediaType.APPLICATION_JSON) .content( objectMapper.writeValueAsString( @@ -624,7 +624,7 @@ class AccountControllerTest @Autowired constructor( @Test fun `should fail due to wrong password`() { mockMvc.perform( - post("/accounts/changePassword/{id}", changePasswordAccount.id) + post("/accounts/{id}/password", changePasswordAccount.id) .contentType(MediaType.APPLICATION_JSON) .content( objectMapper.writeValueAsString( From 5a3b31aba2b55a13ecd7ac42d0412cc2f69cc16a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Costa?= Date: Sat, 22 Jul 2023 16:03:47 +0100 Subject: [PATCH 2/9] Refactor auth endpoints --- .../ni/website/backend/controller/AuthController.kt | 2 +- .../backend/controller/AccountControllerTest.kt | 2 +- .../website/backend/controller/AuthControllerTest.kt | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/AuthController.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/AuthController.kt index e0882b7d..b49cebe5 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/AuthController.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/AuthController.kt @@ -14,7 +14,7 @@ import pt.up.fe.ni.website.backend.service.AuthService @RestController @RequestMapping("/auth") class AuthController(val authService: AuthService) { - @PostMapping("/new") + @PostMapping fun getNewToken(@RequestBody loginDto: LoginDto): Map { val account = authService.authenticate(loginDto.email, loginDto.password) val accessToken = authService.generateAccessToken(account) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AccountControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AccountControllerTest.kt index e7c776f0..6ffef971 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AccountControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AccountControllerTest.kt @@ -610,7 +610,7 @@ class AccountControllerTest @Autowired constructor( documentRequestPayload = true ) - mockMvc.post("/auth/new") { + mockMvc.post("/auth") { contentType = MediaType.APPLICATION_JSON content = objectMapper.writeValueAsString( mapOf( diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AuthControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AuthControllerTest.kt index e046549f..adf6456a 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AuthControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AuthControllerTest.kt @@ -63,7 +63,7 @@ class AuthControllerTest @Autowired constructor( ) @NestedTest - @DisplayName("POST /auth/new") + @DisplayName("POST /auth") inner class GetNewToken { @BeforeEach fun setup() { @@ -75,7 +75,7 @@ class AuthControllerTest @Autowired constructor( @Test fun `should fail when email is not registered`() { mockMvc.perform( - post("/auth/new") + post("/auth") .contentType(MediaType.APPLICATION_JSON) .content( objectMapper.writeValueAsString( @@ -96,7 +96,7 @@ class AuthControllerTest @Autowired constructor( @Test fun `should fail when password is incorrect`() { mockMvc.perform( - post("/auth/new") + post("/auth") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(LoginDto(testAccount.email, "wrong_password"))) ) @@ -110,7 +110,7 @@ class AuthControllerTest @Autowired constructor( @Test fun `should return access and refresh tokens`() { mockMvc.perform( - post("/auth/new") + post("/auth") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(LoginDto(testAccount.email, testPassword))) ) @@ -155,7 +155,7 @@ class AuthControllerTest @Autowired constructor( @Test fun `should return new access token`() { - mockMvc.post("/auth/new") { + mockMvc.post("/auth") { contentType = MediaType.APPLICATION_JSON content = objectMapper.writeValueAsString(LoginDto(testAccount.email, testPassword)) }.andReturn().response.let { response -> @@ -216,7 +216,7 @@ class AuthControllerTest @Autowired constructor( @Test fun `should return authenticated user`() { - mockMvc.post("/auth/new") { + mockMvc.post("/auth") { contentType = MediaType.APPLICATION_JSON content = objectMapper.writeValueAsString(LoginDto(testAccount.email, testPassword)) }.andReturn().response.let { response -> From af590b32fb7d0b70145e1da53f39e8b73c4aa1a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Costa?= Date: Sat, 22 Jul 2023 17:10:32 +0100 Subject: [PATCH 3/9] Refactor events endpoints --- .../backend/controller/EventController.kt | 10 +- .../backend/controller/EventControllerTest.kt | 187 +++++++++--------- 2 files changed, 99 insertions(+), 98 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt index 69692906..0a837ff2 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt @@ -21,7 +21,9 @@ import pt.up.fe.ni.website.backend.utils.validation.ValidImage @Validated class EventController(private val service: EventService) { @GetMapping - fun getAllEvents() = service.getAllEvents() + fun getEvents(@RequestParam(required = false, name = "category") category: String?): List { + return category?.let { service.getEventsByCategory(it) } ?: service.getAllEvents() + } @GetMapping("/{id:\\d+}") fun getEventById(@PathVariable id: Long) = service.getEventById(id) @@ -32,7 +34,7 @@ class EventController(private val service: EventService) { @GetMapping("/{eventSlug}**") fun getEvent(@PathVariable eventSlug: String) = service.getEventBySlug(eventSlug) - @PostMapping("/new", consumes = ["multipart/form-data"]) + @PostMapping(consumes = ["multipart/form-data"]) fun createEvent( @RequestPart event: EventDto, @RequestParam @@ -61,13 +63,13 @@ class EventController(private val service: EventService) { return service.updateEventById(id, event) } - @PutMapping("/{idEvent}/addTeamMember/{idAccount}") + @PutMapping("/{idEvent}/team/{idAccount}") fun addTeamMemberById( @PathVariable idEvent: Long, @PathVariable idAccount: Long ) = service.addTeamMemberById(idEvent, idAccount) - @PutMapping("/{idEvent}/removeTeamMember/{idAccount}") + @DeleteMapping("/{idEvent}/team/{idAccount}") fun removeTeamMemberById( @PathVariable idEvent: Long, @PathVariable idAccount: Long diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt index f8e1e6c2..6a9d6d04 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt @@ -124,6 +124,85 @@ internal class EventControllerTest @Autowired constructor( } } + @NestedTest + @DisplayName("GET events?category={category}") + inner class GetEventsByCategory { + private val testEvents = listOf( + testEvent, + Event( + "Bad event", + "This event was a failure", + mutableListOf(testAccount), + mutableListOf(), + null, + "weird-al.png", + null, + DateInterval( + TestUtils.createDate(2021, Calendar.OCTOBER, 27), + null + ), + null, + null, + ), + Event( + "Mid event", + "This event was ok", + mutableListOf(), + mutableListOf(), + null, + "waldo.jpeg", + null, + DateInterval( + TestUtils.createDate(2022, Calendar.JANUARY, 15), + null + ), + null, + "Other category", + ), + Event( + "Cool event", + "This event was a awesome", + mutableListOf(testAccount), + mutableListOf(), + null, + "ni.png", + null, + DateInterval( + TestUtils.createDate(2022, Calendar.SEPTEMBER, 11), + null + ), + null, + "Great Events", + ) + ) + + private val queryParameters = listOf( + parameterWithName("category").description("Category of the events to retrieve").optional() + ) + + @BeforeEach + fun addToRepositories() { + accountRepository.save(testAccount) + for (event in testEvents) repository.save(event) + } + + @Test + fun `should return all events of the category`() { + mockMvc.perform(get("/events?category={category}", testEvent.category)) + .andExpectAll( + status().isOk, + content().contentType(MediaType.APPLICATION_JSON), + jsonPath("$.length()").value(2), + jsonPath("$[0].category").value(testEvent.category), + jsonPath("$[1].category").value(testEvent.category) + ) + .andDocument( + documentation.getModelDocumentationArray(), + queryParameters = queryParameters + ) + } + } + @NestedTest @DisplayName("GET /events/{id}") inner class GetEventById { @@ -231,87 +310,7 @@ internal class EventControllerTest @Autowired constructor( } @NestedTest - @DisplayName("GET events/category/{category}") - inner class GetEventsByCategory { - private val testEvents = listOf( - testEvent, - Event( - "Bad event", - "This event was a failure", - mutableListOf(testAccount), - mutableListOf(), - null, - "bad-image.png", - null, - DateInterval( - TestUtils.createDate(2021, Calendar.OCTOBER, 27), - null - ), - null, - null - ), - Event( - "Mid event", - "This event was ok", - mutableListOf(), - mutableListOf(), - null, - "mid-image.png", - null, - DateInterval( - TestUtils.createDate(2022, Calendar.JANUARY, 15), - null - ), - null, - "Other category" - ), - Event( - "Cool event", - "This event was a awesome", - mutableListOf(testAccount), - mutableListOf(), - null, - "cool-image.png", - null, - DateInterval( - TestUtils.createDate(2022, Calendar.SEPTEMBER, 11), - null - ), - null, - "Great Events" - ) - ) - - private val parameters = listOf(parameterWithName("category").description("Category of the events to retrieve")) - - @BeforeEach - fun addToRepositories() { - accountRepository.save(testAccount) - for (event in testEvents) repository.save(event) - } - - @Test - fun `should return all events of the category`() { - mockMvc.perform(get("/events/category/{category}", testEvent.category)) - .andExpectAll( - status().isOk, - content().contentType(MediaType.APPLICATION_JSON), - jsonPath("$.length()").value(2), - jsonPath("$[0].category").value(testEvent.category), - jsonPath("$[1].category").value(testEvent.category) - ) - .andDocument( - documentation.getModelDocumentationArray(), - "Get events by category", - "This endpoint allows the retrieval of events labeled with a given category. " + - "It might be used to filter events in the event page.", - urlParameters = parameters - ) - } - } - - @NestedTest - @DisplayName("POST /events/new") + @DisplayName("POST /events") inner class CreateEvent { private val uuid: UUID = UUID.randomUUID() private val mockedSettings = Mockito.mockStatic(UUID::class.java) @@ -347,7 +346,7 @@ internal class EventControllerTest @Autowired constructor( ) ) - mockMvc.multipartBuilder("/events/new") + mockMvc.multipartBuilder("/events") .addPart("event", eventPart) .addFile(name = "image") .perform() @@ -392,13 +391,13 @@ internal class EventControllerTest @Autowired constructor( "Great Events" ) - mockMvc.multipartBuilder("/events/new") + mockMvc.multipartBuilder("/events") .addPart("event", objectMapper.writeValueAsString(testEvent)) .addFile(name = "image") .perform() .andExpect { status().isOk } - mockMvc.multipartBuilder("/events/new") + mockMvc.multipartBuilder("/events") .addPart("event", objectMapper.writeValueAsString(duplicatedSlugEvent)) .addFile(name = "image") .perform() @@ -413,7 +412,7 @@ internal class EventControllerTest @Autowired constructor( @Test fun `should fail to create event with invalid filename extension`() { - mockMvc.multipartBuilder("/events/new") + mockMvc.multipartBuilder("/events") .addPart("event", objectMapper.writeValueAsString(testEvent)) .addFile(name = "image", filename = "image.pdf") .perform() @@ -429,7 +428,7 @@ internal class EventControllerTest @Autowired constructor( @Test fun `should fail to create event with invalid filename media type`() { - mockMvc.multipartBuilder("/events/new") + mockMvc.multipartBuilder("/events") .addPart("event", objectMapper.writeValueAsString(testEvent)) .addFile(name = "image", contentType = MediaType.APPLICATION_PDF_VALUE) .perform() @@ -445,7 +444,7 @@ internal class EventControllerTest @Autowired constructor( @Test fun `should fail when missing event part`() { - mockMvc.multipartBuilder("/events/new") + mockMvc.multipartBuilder("/events") .addFile(name = "image") .perform() .andExpectAll( @@ -462,7 +461,7 @@ internal class EventControllerTest @Autowired constructor( inner class InputValidation { private val validationTester = ValidationTester( req = { params: Map -> - mockMvc.multipartBuilder("/events/new") + mockMvc.multipartBuilder("/events") .addPart("event", objectMapper.writeValueAsString(params)) .addFile(name = "image") .perform() @@ -641,7 +640,7 @@ internal class EventControllerTest @Autowired constructor( } @NestedTest - @DisplayName("PUT /events/{eventId}/addTeamMember/{accountId}") + @DisplayName("PUT /events/{eventId}/team/{accountId}") inner class AddTeamMember { private val newAccount = Account( @@ -673,7 +672,7 @@ internal class EventControllerTest @Autowired constructor( @Test fun `should add a team member`() { - mockMvc.perform(put("/events/{eventId}/addTeamMember/{accountId}", testEvent.id, newAccount.id)) + mockMvc.perform(put("/events/{eventId}/team/{accountId}", testEvent.id, newAccount.id)) .andExpectAll( status().isOk, content().contentType(MediaType.APPLICATION_JSON), @@ -708,7 +707,7 @@ internal class EventControllerTest @Autowired constructor( @Test fun `should fail if the team member does not exist`() { - mockMvc.perform(put("/events/{eventId}/addTeamMember/{accountId}", testEvent.id, 1234)) + mockMvc.perform(put("/events/{eventId}/team/{accountId}", testEvent.id, 1234)) .andExpectAll( status().isNotFound, content().contentType(MediaType.APPLICATION_JSON), @@ -720,7 +719,7 @@ internal class EventControllerTest @Autowired constructor( } @NestedTest - @DisplayName("PUT /events/{projectId}/removeTeamMember/{accountId}") + @DisplayName("DELETE /events/{projectId}/team/{accountId}") inner class RemoveTeamMember { @BeforeEach @@ -737,7 +736,7 @@ internal class EventControllerTest @Autowired constructor( @Test fun `should remove a team member`() { - mockMvc.perform(put("/events/{eventId}/removeTeamMember/{accountId}", testEvent.id, testAccount.id)) + mockMvc.perform(delete("/events/{eventId}/team/{accountId}", testEvent.id, testAccount.id)) .andExpectAll( status().isOk, content().contentType(MediaType.APPLICATION_JSON), @@ -753,7 +752,7 @@ internal class EventControllerTest @Autowired constructor( @Test fun `should fail if the team member does not exist`() { - mockMvc.perform(put("/events/{eventId}/removeTeamMember/{accountId}", testEvent.id, 1234)) + mockMvc.perform(delete("/events/{eventId}/team/{accountId}", testEvent.id, 1234)) .andExpectAll( status().isNotFound, content().contentType(MediaType.APPLICATION_JSON), From d98d23adcd6712312a20eb55e46fd4794f4c9f60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Costa?= Date: Sat, 22 Jul 2023 17:25:23 +0100 Subject: [PATCH 4/9] Add documentation support for query parameters --- .../backend/controller/EventControllerTest.kt | 5 +++-- .../utils/documentation/utils/MockMVCExtension.kt | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt index 6a9d6d04..dc5eb200 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt @@ -1,5 +1,6 @@ package pt.up.fe.ni.website.backend.controller +import pt.up.fe.ni.website.backend.model.constants.EventConstants as Constants import com.epages.restdocs.apispec.ResourceDocumentation.parameterWithName import com.fasterxml.jackson.databind.ObjectMapper import java.util.Calendar @@ -26,7 +27,6 @@ import pt.up.fe.ni.website.backend.model.Account import pt.up.fe.ni.website.backend.model.CustomWebsite import pt.up.fe.ni.website.backend.model.Event import pt.up.fe.ni.website.backend.model.constants.ActivityConstants -import pt.up.fe.ni.website.backend.model.constants.EventConstants as Constants import pt.up.fe.ni.website.backend.model.embeddable.DateInterval import pt.up.fe.ni.website.backend.repository.AccountRepository import pt.up.fe.ni.website.backend.repository.EventRepository @@ -116,8 +116,9 @@ internal class EventControllerTest @Autowired constructor( .andExpect(content().json(objectMapper.writeValueAsString(testEvents))) .andDocument( documentation.getModelDocumentationArray(), - "Get all the events", + "Get all the events or filter them by category", """The operation returns an array of events, allowing to easily retrieve all the created events. + |It also allows to filter the events by category, using the query parameter "category". |This is useful for example in the frontend's event page, where events are displayed. """.trimMargin() ) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/utils/MockMVCExtension.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/utils/MockMVCExtension.kt index 8d1d6d6d..b1358893 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/utils/MockMVCExtension.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/utils/MockMVCExtension.kt @@ -23,6 +23,7 @@ class MockMVCExtension { requestHeaders: List = emptyList(), responseHeaders: List = emptyList(), urlParameters: List = emptyList(), + queryParameters: List = emptyList(), documentRequestPayload: Boolean = false, hasRequestPayload: Boolean = false ): ResultActions { @@ -31,6 +32,7 @@ class MockMVCExtension { summary, description, urlParameters, + queryParameters, documentation.payload.Request().schema(), documentation.payload.Request().getSchemaFieldDescriptors(), requestHeaders, @@ -50,6 +52,7 @@ class MockMVCExtension { requestHeaders: List = emptyList(), responseHeaders: List = emptyList(), urlParameters: List = emptyList(), + queryParameters: List = emptyList(), documentRequestPayload: Boolean = false, hasRequestPayload: Boolean = false ): ResultActions { @@ -58,6 +61,7 @@ class MockMVCExtension { summary, description, urlParameters, + queryParameters, requestPayload.Request().schema(), requestPayload.Request().getSchemaFieldDescriptors(), requestHeaders, @@ -75,6 +79,7 @@ class MockMVCExtension { description: String? = null, requestHeaders: List = emptyList(), urlParameters: List = emptyList(), + queryParameters: List = emptyList(), documentRequestPayload: Boolean = false, hasRequestPayload: Boolean = false ): ResultActions { @@ -83,6 +88,7 @@ class MockMVCExtension { summary, description, urlParameters, + queryParameters, documentation.payload.Request().schema(), documentation.payload.Request().getSchemaFieldDescriptors(), requestHeaders, @@ -101,6 +107,7 @@ class MockMVCExtension { description: String? = null, requestHeaders: List = emptyList(), urlParameters: List = emptyList(), + queryParameters: List = emptyList(), documentRequestPayload: Boolean = false, hasRequestPayload: Boolean = false ): ResultActions { @@ -109,6 +116,7 @@ class MockMVCExtension { summary, description, urlParameters, + queryParameters, requestPayload.Request().schema(), requestPayload.Request().getSchemaFieldDescriptors(), requestHeaders, @@ -126,6 +134,7 @@ class MockMVCExtension { description: String? = null, requestHeaders: List = emptyList(), urlParameters: List = emptyList(), + queryParameters: List = emptyList(), documentRequestPayload: Boolean = false, hasRequestPayload: Boolean = false ): ResultActions { @@ -134,6 +143,7 @@ class MockMVCExtension { summary, description, urlParameters, + queryParameters, documentation.payload.Request().schema(), documentation.payload.Request().getSchemaFieldDescriptors(), requestHeaders, @@ -152,6 +162,7 @@ class MockMVCExtension { description: String? = null, requestHeaders: List = emptyList(), urlParameters: List = emptyList(), + queryParameters: List = emptyList(), documentRequestPayload: Boolean = false, hasRequestPayload: Boolean = false ): ResultActions { @@ -160,6 +171,7 @@ class MockMVCExtension { summary, description, urlParameters, + queryParameters, requestPayload.Request().schema(), requestPayload.Request().getSchemaFieldDescriptors(), requestHeaders, @@ -176,6 +188,7 @@ class MockMVCExtension { summary: String?, description: String?, urlParameters: List, + queryParameters: List, requestSchema: Schema, requestFields: List, requestHeaders: List, @@ -202,7 +215,7 @@ class MockMVCExtension { builder.requestFields(requestFields) } - builder.requestHeaders(requestHeaders).pathParameters(urlParameters) + builder.requestHeaders(requestHeaders).pathParameters(urlParameters).queryParameters(queryParameters) builder.responseSchema(responseSchema).responseFields(responseFields).responseHeaders(responseHeaders) From b7f9acc716f92ecf9fc42abc1748e4ae197eb9c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Costa?= Date: Sat, 22 Jul 2023 17:44:03 +0100 Subject: [PATCH 5/9] Refactor generations endpoints --- .../controller/GenerationController.kt | 6 ++--- .../backend/service/GenerationService.kt | 2 +- .../controller/GenerationControllerTest.kt | 26 +++++++++---------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/GenerationController.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/GenerationController.kt index d95984d8..7f247802 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/GenerationController.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/GenerationController.kt @@ -16,8 +16,8 @@ import pt.up.fe.ni.website.backend.service.GenerationService @RestController @RequestMapping("/generations") class GenerationController(private val service: GenerationService) { - @GetMapping - fun getAllGenerations() = service.getAllGenerations() + @GetMapping("/years") + fun getGenerationsSchoolYears() = service.getGenerationsSchoolYears() @GetMapping("/{id:\\d+}") fun getGenerationById(@PathVariable id: Long) = service.getGenerationById(id) @@ -28,7 +28,7 @@ class GenerationController(private val service: GenerationService) { @GetMapping("/latest") fun getLatestGeneration() = service.getLatestGeneration() - @PostMapping("/new") + @PostMapping fun createNewGeneration( @RequestBody dto: GenerationDto ) = service.createNewGeneration(dto) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/GenerationService.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/GenerationService.kt index 5cf6e985..7d5733d7 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/GenerationService.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/GenerationService.kt @@ -19,7 +19,7 @@ class GenerationService( private val activityService: ActivityService ) { - fun getAllGenerations(): List = repository.findAllSchoolYearOrdered() + fun getGenerationsSchoolYears(): List = repository.findAllSchoolYearOrdered() fun getGenerationById(id: Long): GetGenerationDto { val generation = diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/GenerationControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/GenerationControllerTest.kt index e3df1711..a53a7925 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/GenerationControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/GenerationControllerTest.kt @@ -93,7 +93,7 @@ class GenerationControllerTest @Autowired constructor( ) @NestedTest - @DisplayName("GET /generations") + @DisplayName("GET /generations/years") inner class GetAllGenerations { @BeforeEach @@ -105,7 +105,7 @@ class GenerationControllerTest @Autowired constructor( @Test fun `should return all generation years`() { - mockMvc.perform(get("/generations")) + mockMvc.perform(get("/generations/years")) .andExpectAll( status().isOk, content().contentType(MediaType.APPLICATION_JSON), @@ -124,7 +124,7 @@ class GenerationControllerTest @Autowired constructor( } @NestedTest - @DisplayName("GET /generations/year") + @DisplayName("GET /generations/{year}") inner class GetGenerationByYear { @BeforeEach fun addGenerations() { @@ -393,12 +393,12 @@ class GenerationControllerTest @Autowired constructor( } @NestedTest - @DisplayName("POST /generations/new") + @DisplayName("POST /generations") inner class CreateGeneration { @Test fun `should create a new generation`() { mockMvc.perform( - post("/generations/new").contentType(MediaType.APPLICATION_JSON).content( + post("/generations").contentType(MediaType.APPLICATION_JSON).content( objectMapper.writeValueAsString(GenerationDto("22-23", emptyList())) ) ) @@ -438,7 +438,7 @@ class GenerationControllerTest @Autowired constructor( ) mockMvc.perform( - post("/generations/new") + post("/generations") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(generationDtoWithRoles)) ) @@ -484,7 +484,7 @@ class GenerationControllerTest @Autowired constructor( ) mockMvc.perform( - post("/generations/new") + post("/generations") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(generationDtoWithRoles)) ) @@ -511,7 +511,7 @@ class GenerationControllerTest @Autowired constructor( @Test fun `should fail if year is not specified and there are no generations`() { mockMvc.perform( - post("/generations/new") + post("/generations") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(GenerationDto(null, emptyList()))) ) @@ -536,7 +536,7 @@ class GenerationControllerTest @Autowired constructor( @Test fun `should infer the year if not specified and create generation`() { mockMvc.perform( - post("/generations/new") + post("/generations") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(GenerationDto(null, emptyList()))) ) @@ -564,7 +564,7 @@ class GenerationControllerTest @Autowired constructor( ) mockMvc.perform( - post("/generations/new") + post("/generations") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(generationDtoWithAccounts)) ) @@ -615,7 +615,7 @@ class GenerationControllerTest @Autowired constructor( ) mockMvc.perform( - post("/generations/new") + post("/generations") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(generationDtoWithActivities)) ) @@ -669,7 +669,7 @@ class GenerationControllerTest @Autowired constructor( @Test fun `should fail if the year already exists`() { mockMvc.perform( - post("/generations/new") + post("/generations") .contentType(MediaType.APPLICATION_JSON) .content( objectMapper.writeValueAsString(GenerationDto("22-23", emptyList())) @@ -686,7 +686,7 @@ class GenerationControllerTest @Autowired constructor( } @NestedTest - @DisplayName("PATCH /generations/year") + @DisplayName("PATCH /generations/{year}") inner class UpdateGenerationByYear { @BeforeEach fun addGenerations() { From f281ac512fc8f011968891da4386311fc2e04f8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Costa?= Date: Sat, 22 Jul 2023 17:55:40 +0100 Subject: [PATCH 6/9] Refactor posts endpoints --- .../controller/GenerationController.kt | 4 +-- .../backend/controller/PostController.kt | 2 +- .../backend/controller/ProjectController.kt | 6 ++-- .../backend/service/GenerationService.kt | 2 +- .../controller/AccountControllerTest.kt | 2 +- .../backend/controller/EventControllerTest.kt | 8 +++--- .../controller/GenerationControllerTest.kt | 4 +-- .../backend/controller/PostControllerTest.kt | 10 +++---- .../controller/ProjectControllerTest.kt | 28 +++++++++---------- 9 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/GenerationController.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/GenerationController.kt index 7f247802..c3c7db61 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/GenerationController.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/GenerationController.kt @@ -16,8 +16,8 @@ import pt.up.fe.ni.website.backend.service.GenerationService @RestController @RequestMapping("/generations") class GenerationController(private val service: GenerationService) { - @GetMapping("/years") - fun getGenerationsSchoolYears() = service.getGenerationsSchoolYears() + @GetMapping + fun getAllGenerations() = service.getAllGenerations() @GetMapping("/{id:\\d+}") fun getGenerationById(@PathVariable id: Long) = service.getGenerationById(id) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/PostController.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/PostController.kt index efb1dbef..de90e257 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/PostController.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/PostController.kt @@ -24,7 +24,7 @@ class PostController(private val service: PostService) { @GetMapping("/{postSlug}**") fun getPost(@PathVariable postSlug: String) = service.getPostBySlug(postSlug) - @PostMapping("/new") + @PostMapping fun createPost(@RequestBody dto: PostDto) = service.createPost(dto) @PutMapping("/{postId}") diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/ProjectController.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/ProjectController.kt index 053b0cad..cdb36031 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/ProjectController.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/ProjectController.kt @@ -30,7 +30,7 @@ class ProjectController(private val service: ProjectService) { @GetMapping("/{projectSlug}**") fun getProjectBySlug(@PathVariable projectSlug: String) = service.getProjectBySlug(projectSlug) - @PostMapping("/new", consumes = ["multipart/form-data"]) + @PostMapping(consumes = ["multipart/form-data"]) fun createProject( @RequestPart project: ProjectDto, @RequestParam @@ -65,13 +65,13 @@ class ProjectController(private val service: ProjectService) { @PutMapping("/{id}/unarchive") fun unarchiveProjectById(@PathVariable id: Long) = service.unarchiveProjectById(id) - @PutMapping("/{idProject}/addTeamMember/{idAccount}") + @PutMapping("/{idProject}/team/{idAccount}") fun addTeamMemberById( @PathVariable idProject: Long, @PathVariable idAccount: Long ) = service.addTeamMemberById(idProject, idAccount) - @PutMapping("/{idProject}/removeTeamMember/{idAccount}") + @DeleteMapping("/{idProject}/team/{idAccount}") fun removeTeamMemberById( @PathVariable idProject: Long, @PathVariable idAccount: Long diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/GenerationService.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/GenerationService.kt index 7d5733d7..5cf6e985 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/GenerationService.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/GenerationService.kt @@ -19,7 +19,7 @@ class GenerationService( private val activityService: ActivityService ) { - fun getGenerationsSchoolYears(): List = repository.findAllSchoolYearOrdered() + fun getAllGenerations(): List = repository.findAllSchoolYearOrdered() fun getGenerationById(id: Long): GetGenerationDto { val generation = diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AccountControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AccountControllerTest.kt index 6ffef971..41d21c12 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AccountControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AccountControllerTest.kt @@ -1,6 +1,5 @@ package pt.up.fe.ni.website.backend.controller -import pt.up.fe.ni.website.backend.model.constants.AccountConstants as Constants import com.epages.restdocs.apispec.ResourceDocumentation.parameterWithName import com.fasterxml.jackson.databind.ObjectMapper import java.util.Calendar @@ -30,6 +29,7 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status import pt.up.fe.ni.website.backend.config.upload.UploadConfigProperties import pt.up.fe.ni.website.backend.model.Account import pt.up.fe.ni.website.backend.model.CustomWebsite +import pt.up.fe.ni.website.backend.model.constants.AccountConstants as Constants import pt.up.fe.ni.website.backend.repository.AccountRepository import pt.up.fe.ni.website.backend.utils.TestUtils import pt.up.fe.ni.website.backend.utils.ValidationTester diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt index dc5eb200..85bd2afb 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt @@ -1,6 +1,5 @@ package pt.up.fe.ni.website.backend.controller -import pt.up.fe.ni.website.backend.model.constants.EventConstants as Constants import com.epages.restdocs.apispec.ResourceDocumentation.parameterWithName import com.fasterxml.jackson.databind.ObjectMapper import java.util.Calendar @@ -27,6 +26,7 @@ import pt.up.fe.ni.website.backend.model.Account import pt.up.fe.ni.website.backend.model.CustomWebsite import pt.up.fe.ni.website.backend.model.Event import pt.up.fe.ni.website.backend.model.constants.ActivityConstants +import pt.up.fe.ni.website.backend.model.constants.EventConstants as Constants import pt.up.fe.ni.website.backend.model.embeddable.DateInterval import pt.up.fe.ni.website.backend.repository.AccountRepository import pt.up.fe.ni.website.backend.repository.EventRepository @@ -143,7 +143,7 @@ internal class EventControllerTest @Autowired constructor( null ), null, - null, + null ), Event( "Mid event", @@ -158,7 +158,7 @@ internal class EventControllerTest @Autowired constructor( null ), null, - "Other category", + "Other category" ), Event( "Cool event", @@ -173,7 +173,7 @@ internal class EventControllerTest @Autowired constructor( null ), null, - "Great Events", + "Great Events" ) ) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/GenerationControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/GenerationControllerTest.kt index a53a7925..0b9c98ab 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/GenerationControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/GenerationControllerTest.kt @@ -93,7 +93,7 @@ class GenerationControllerTest @Autowired constructor( ) @NestedTest - @DisplayName("GET /generations/years") + @DisplayName("GET /generations") inner class GetAllGenerations { @BeforeEach @@ -105,7 +105,7 @@ class GenerationControllerTest @Autowired constructor( @Test fun `should return all generation years`() { - mockMvc.perform(get("/generations/years")) + mockMvc.perform(get("/generations")) .andExpectAll( status().isOk, content().contentType(MediaType.APPLICATION_JSON), diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/PostControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/PostControllerTest.kt index ee616532..a21edf3e 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/PostControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/PostControllerTest.kt @@ -187,7 +187,7 @@ internal class PostControllerTest @Autowired constructor( } @NestedTest - @DisplayName("POST /posts/new") + @DisplayName("POST /posts") inner class CreatePost { @BeforeEach fun clearPosts() { @@ -197,7 +197,7 @@ internal class PostControllerTest @Autowired constructor( @Test fun `should create a new post`() { mockMvc.perform( - post("/posts/new") + post("/posts") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(testPost)) ) @@ -221,13 +221,13 @@ internal class PostControllerTest @Autowired constructor( @Test fun `should fail to create post with existing slug`() { - mockMvc.post("/posts/new") { + mockMvc.post("/posts") { contentType = MediaType.APPLICATION_JSON content = objectMapper.writeValueAsString(testPost) }.andExpect { status { isOk() } } mockMvc.perform( - post("/posts/new") + post("/posts") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(testPost)) ) @@ -246,7 +246,7 @@ internal class PostControllerTest @Autowired constructor( private val validationTester = ValidationTester( req = { params: Map -> mockMvc.perform( - post("/posts/new") + post("/posts") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(params)) ) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt index 132ff20c..eaa2c733 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt @@ -274,7 +274,7 @@ internal class ProjectControllerTest @Autowired constructor( } @NestedTest - @DisplayName("POST /projects/new") + @DisplayName("POST /projects") inner class CreateProject { private val uuid: UUID = UUID.randomUUID() private val mockedSettings = Mockito.mockStatic(UUID::class.java) @@ -315,7 +315,7 @@ internal class ProjectControllerTest @Autowired constructor( ) ) - mockMvc.multipartBuilder("/projects/new") + mockMvc.multipartBuilder("/projects") .addPart("project", projectPart) .addFile(name = "image") .perform() @@ -368,13 +368,13 @@ internal class ProjectControllerTest @Autowired constructor( mutableListOf(testAccount) ) - mockMvc.multipartBuilder("/projects/new") + mockMvc.multipartBuilder("/projects") .addPart("project", objectMapper.writeValueAsString(testProject)) .addFile(name = "image") .perform() .andExpect { status().isOk } - mockMvc.multipartBuilder("/projects/new") + mockMvc.multipartBuilder("/projects") .addPart("project", objectMapper.writeValueAsString(duplicatedSlugProject)) .addFile(name = "image") .perform() @@ -389,7 +389,7 @@ internal class ProjectControllerTest @Autowired constructor( @Test fun `should fail to create project with invalid filename extension`() { - mockMvc.multipartBuilder("/projects/new") + mockMvc.multipartBuilder("/projects") .addPart("project", objectMapper.writeValueAsString(testProject)) .addFile(name = "image", filename = "image.pdf") .perform() @@ -405,7 +405,7 @@ internal class ProjectControllerTest @Autowired constructor( @Test fun `should fail to create project with invalid filename media type`() { - mockMvc.multipartBuilder("/projects/new") + mockMvc.multipartBuilder("/projects") .addPart("project", objectMapper.writeValueAsString(testProject)) .addFile(name = "image", contentType = MediaType.APPLICATION_PDF_VALUE) .perform() @@ -421,7 +421,7 @@ internal class ProjectControllerTest @Autowired constructor( @Test fun `should fail when missing project part`() { - mockMvc.multipartBuilder("/projects/new") + mockMvc.multipartBuilder("/projects") .addFile(name = "image") .perform() .andExpectAll( @@ -438,7 +438,7 @@ internal class ProjectControllerTest @Autowired constructor( inner class InputValidation { private val validationTester = ValidationTester( req = { params: Map -> - mockMvc.multipartBuilder("/projects/new") + mockMvc.multipartBuilder("/projects") .addPart("project", objectMapper.writeValueAsString(params)) .addFile(name = "image") .perform() @@ -1289,7 +1289,7 @@ internal class ProjectControllerTest @Autowired constructor( } @NestedTest - @DisplayName("PUT /projects/{projectId}/addTeamMember/{accountId}") + @DisplayName("PUT /projects/{projectId}/team/{accountId}") inner class AddTeamMember { private val newAccount = Account( @@ -1324,7 +1324,7 @@ internal class ProjectControllerTest @Autowired constructor( @Test fun `should add a team member`() { mockMvc.perform( - put("/projects/{projectId}/addTeamMember/{accountId}", testProject.id, newAccount.id) + put("/projects/{projectId}/team/{accountId}", testProject.id, newAccount.id) ) .andExpectAll( status().isOk, content().contentType(MediaType.APPLICATION_JSON), @@ -1358,7 +1358,7 @@ internal class ProjectControllerTest @Autowired constructor( @Test fun `should fail if the team member does not exist`() { - mockMvc.perform(put("/projects/{projectId}/addTeamMember/{accountId}", testProject.id, 1234)) + mockMvc.perform(put("/projects/{projectId}/team/{accountId}", testProject.id, 1234)) .andExpectAll( status().isNotFound, content().contentType(MediaType.APPLICATION_JSON), @@ -1373,7 +1373,7 @@ internal class ProjectControllerTest @Autowired constructor( } @NestedTest - @DisplayName("PUT /projects/{projectId}/removeTeamMember/{accountId}") + @DisplayName("DELETE /projects/{projectId}/team/{accountId}") inner class RemoveTeamMember { @BeforeEach @@ -1392,7 +1392,7 @@ internal class ProjectControllerTest @Autowired constructor( @Test fun `should remove a team member`() { - mockMvc.perform(put("/projects/{projectId}/removeTeamMember/{accountId}", testProject.id, testAccount.id)) + mockMvc.perform(delete("/projects/{projectId}/team/{accountId}", testProject.id, testAccount.id)) .andExpectAll( status().isOk, content().contentType(MediaType.APPLICATION_JSON), @@ -1408,7 +1408,7 @@ internal class ProjectControllerTest @Autowired constructor( @Test fun `should fail if the team member does not exist`() { - mockMvc.perform(put("/projects/{projectId}/removeTeamMember/{accountId}", testProject.id, 1234)) + mockMvc.perform(delete("/projects/{projectId}/team/{accountId}", testProject.id, 1234)) .andExpectAll( status().isNotFound, content().contentType(MediaType.APPLICATION_JSON), From bcd30ae259d4c55a620e60f64af1c18bb8b32a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Costa?= Date: Sat, 7 Oct 2023 15:48:08 +0100 Subject: [PATCH 7/9] Merge get all events and get events by category tests --- .../backend/controller/EventControllerTest.kt | 120 ++++++------------ 1 file changed, 41 insertions(+), 79 deletions(-) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt index 85bd2afb..f161af80 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt @@ -27,6 +27,7 @@ import pt.up.fe.ni.website.backend.model.CustomWebsite import pt.up.fe.ni.website.backend.model.Event import pt.up.fe.ni.website.backend.model.constants.ActivityConstants import pt.up.fe.ni.website.backend.model.constants.EventConstants as Constants +import org.junit.jupiter.api.Nested import pt.up.fe.ni.website.backend.model.embeddable.DateInterval import pt.up.fe.ni.website.backend.repository.AccountRepository import pt.up.fe.ni.website.backend.repository.EventRepository @@ -80,26 +81,11 @@ internal class EventControllerTest @Autowired constructor( val documentation = PayloadEvent() - @NestedTest - @DisplayName("GET /events") - inner class GetAllEvents { - private val testEvents = listOf( - testEvent, - Event( - "Bad event", - "This event was a failure", - mutableListOf(), - mutableListOf(), - null, - "bad-image.png", - null, - DateInterval( - TestUtils.createDate(2021, Calendar.OCTOBER, 27), - null - ), - null, - null - ) + @DisplayName("GET events?category={category}") + @Nested + inner class GetEvents { + private val testEvents = mutableListOf( + testEvent ) @BeforeEach @@ -123,72 +109,48 @@ internal class EventControllerTest @Autowired constructor( """.trimMargin() ) } - } - - @NestedTest - @DisplayName("GET events?category={category}") - inner class GetEventsByCategory { - private val testEvents = listOf( - testEvent, - Event( - "Bad event", - "This event was a failure", - mutableListOf(testAccount), - mutableListOf(), - null, - "weird-al.png", - null, - DateInterval( - TestUtils.createDate(2021, Calendar.OCTOBER, 27), - null - ), - null, - null - ), - Event( - "Mid event", - "This event was ok", - mutableListOf(), - mutableListOf(), - null, - "waldo.jpeg", - null, - DateInterval( - TestUtils.createDate(2022, Calendar.JANUARY, 15), - null - ), - null, - "Other category" - ), - Event( - "Cool event", - "This event was a awesome", - mutableListOf(testAccount), - mutableListOf(), - null, - "ni.png", - null, - DateInterval( - TestUtils.createDate(2022, Calendar.SEPTEMBER, 11), - null - ), - null, - "Great Events" - ) - ) private val queryParameters = listOf( parameterWithName("category").description("Category of the events to retrieve").optional() ) - @BeforeEach - fun addToRepositories() { - accountRepository.save(testAccount) - for (event in testEvents) repository.save(event) - } - @Test fun `should return all events of the category`() { + val extraEvents = listOf( + Event( + "Mid event", + "This event was ok", + mutableListOf(), + mutableListOf(), + "bloat", + "waldo.jpeg", + null, + DateInterval( + TestUtils.createDate(2022, Calendar.JANUARY, 15), + null + ), + "FCUP", + "Other category" + ), + Event( + "Cool event", + "This event was a awesome", + mutableListOf(testAccount), + mutableListOf(), + "ni", + "ni.png", + null, + DateInterval( + TestUtils.createDate(2022, Calendar.SEPTEMBER, 11), + null + ), + "NI", + "Great Events" + ) + ) + extraEvents.forEach { repository.save(it) } + + mockMvc.perform(get("/events?category={category}", testEvent.category)) .andExpectAll( status().isOk, From 06838cb517a36c6a6a13f4deb140360f30bd8f41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Costa?= Date: Sat, 7 Oct 2023 15:53:08 +0100 Subject: [PATCH 8/9] Refactor hall of fame endpoints --- .../website/backend/controller/ProjectController.kt | 4 ++-- .../backend/controller/EventControllerTest.kt | 3 +-- .../backend/controller/ProjectControllerTest.kt | 12 ++++++------ 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/ProjectController.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/ProjectController.kt index cdb36031..2669fdcc 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/ProjectController.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/ProjectController.kt @@ -77,13 +77,13 @@ class ProjectController(private val service: ProjectService) { @PathVariable idAccount: Long ) = service.removeTeamMemberById(idProject, idAccount) - @PutMapping("/{idProject}/addHallOfFameMember/{idAccount}") + @PutMapping("/{idProject}/hallOfFameMember/{idAccount}") fun addHallOfFameMemberById( @PathVariable idProject: Long, @PathVariable idAccount: Long ) = service.addHallOfFameMemberById(idProject, idAccount) - @PutMapping("/{idProject}/removeHallOfFameMember/{idAccount}") + @DeleteMapping("/{idProject}/hallOfFameMember/{idAccount}") fun removeHallOfFameMemberById( @PathVariable idProject: Long, @PathVariable idAccount: Long diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt index f161af80..894959a6 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt @@ -10,6 +10,7 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.mockito.Mockito import org.springframework.beans.factory.annotation.Autowired @@ -27,7 +28,6 @@ import pt.up.fe.ni.website.backend.model.CustomWebsite import pt.up.fe.ni.website.backend.model.Event import pt.up.fe.ni.website.backend.model.constants.ActivityConstants import pt.up.fe.ni.website.backend.model.constants.EventConstants as Constants -import org.junit.jupiter.api.Nested import pt.up.fe.ni.website.backend.model.embeddable.DateInterval import pt.up.fe.ni.website.backend.repository.AccountRepository import pt.up.fe.ni.website.backend.repository.EventRepository @@ -150,7 +150,6 @@ internal class EventControllerTest @Autowired constructor( ) extraEvents.forEach { repository.save(it) } - mockMvc.perform(get("/events?category={category}", testEvent.category)) .andExpectAll( status().isOk, diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt index eaa2c733..c457c72c 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt @@ -1423,7 +1423,7 @@ internal class ProjectControllerTest @Autowired constructor( } @NestedTest - @DisplayName("PUT /projects/{idProject}/addHallOfFameMember/{idAccount}") + @DisplayName("PUT /projects/{idProject}/hallOfFameMember/{idAccount}") inner class AddHallOfFameMember { private val newAccount = Account( "Another test Account", @@ -1457,7 +1457,7 @@ internal class ProjectControllerTest @Autowired constructor( @Test fun `should add account to project's hall of fame`() { mockMvc.perform( - put("/projects/{idProject}/addHallOfFameMember/{idAccount}", testProject.id, newAccount.id) + put("/projects/{idProject}/hallOfFameMember/{idAccount}", testProject.id, newAccount.id) ) .andExpectAll( status().isOk, content().contentType(MediaType.APPLICATION_JSON), @@ -1491,7 +1491,7 @@ internal class ProjectControllerTest @Autowired constructor( @Test fun `should fail if the account does not exist`() { - mockMvc.perform(put("/projects/{idProject}/addHallOfFameMember/{idAccount}", testProject.id, 1234)) + mockMvc.perform(put("/projects/{idProject}/hallOfFameMember/{idAccount}", testProject.id, 1234)) .andExpectAll( status().isNotFound, content().contentType(MediaType.APPLICATION_JSON), @@ -1506,7 +1506,7 @@ internal class ProjectControllerTest @Autowired constructor( } @NestedTest - @DisplayName("PUT /projects/{idProject}/removeHallOfFameMember/{idAccount}") + @DisplayName("DELETE /projects/{idProject}/hallOfFameMember/{idAccount}") inner class RemoveHallOfFameMember { @BeforeEach fun addToRepositories() { @@ -1525,7 +1525,7 @@ internal class ProjectControllerTest @Autowired constructor( @Test fun `should remove an account from project's hall of fame`() { mockMvc.perform( - put("/projects/{idProject}/removeHallOfFameMember/{idAccount}", testProject.id, testAccount2.id) + delete("/projects/{idProject}/hallOfFameMember/{idAccount}", testProject.id, testAccount2.id) ) .andExpectAll( status().isOk, @@ -1542,7 +1542,7 @@ internal class ProjectControllerTest @Autowired constructor( @Test fun `should fail if the account does not exist`() { - mockMvc.perform(put("/projects/{idProject}/removeHallOfFameMember/{idAccount}", testProject.id, 1234)) + mockMvc.perform(delete("/projects/{idProject}/hallOfFameMember/{idAccount}", testProject.id, 1234)) .andExpectAll( status().isNotFound, content().contentType(MediaType.APPLICATION_JSON), From 2f4c835aece8a9f9e96b2066212821cac64caa30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Costa?= Date: Tue, 10 Oct 2023 00:22:47 +0100 Subject: [PATCH 9/9] Normalize hall of fame endpoints --- .../website/backend/controller/ProjectController.kt | 4 ++-- .../backend/controller/ProjectControllerTest.kt | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/ProjectController.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/ProjectController.kt index 2669fdcc..ab4a2c06 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/ProjectController.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/ProjectController.kt @@ -77,13 +77,13 @@ class ProjectController(private val service: ProjectService) { @PathVariable idAccount: Long ) = service.removeTeamMemberById(idProject, idAccount) - @PutMapping("/{idProject}/hallOfFameMember/{idAccount}") + @PutMapping("/{idProject}/hallOfFame/{idAccount}") fun addHallOfFameMemberById( @PathVariable idProject: Long, @PathVariable idAccount: Long ) = service.addHallOfFameMemberById(idProject, idAccount) - @DeleteMapping("/{idProject}/hallOfFameMember/{idAccount}") + @DeleteMapping("/{idProject}/hallOfFame/{idAccount}") fun removeHallOfFameMemberById( @PathVariable idProject: Long, @PathVariable idAccount: Long diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt index c457c72c..b5e53364 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt @@ -1423,7 +1423,7 @@ internal class ProjectControllerTest @Autowired constructor( } @NestedTest - @DisplayName("PUT /projects/{idProject}/hallOfFameMember/{idAccount}") + @DisplayName("PUT /projects/{idProject}/hallOfFame/{idAccount}") inner class AddHallOfFameMember { private val newAccount = Account( "Another test Account", @@ -1457,7 +1457,7 @@ internal class ProjectControllerTest @Autowired constructor( @Test fun `should add account to project's hall of fame`() { mockMvc.perform( - put("/projects/{idProject}/hallOfFameMember/{idAccount}", testProject.id, newAccount.id) + put("/projects/{idProject}/hallOfFame/{idAccount}", testProject.id, newAccount.id) ) .andExpectAll( status().isOk, content().contentType(MediaType.APPLICATION_JSON), @@ -1491,7 +1491,7 @@ internal class ProjectControllerTest @Autowired constructor( @Test fun `should fail if the account does not exist`() { - mockMvc.perform(put("/projects/{idProject}/hallOfFameMember/{idAccount}", testProject.id, 1234)) + mockMvc.perform(put("/projects/{idProject}/hallOfFame/{idAccount}", testProject.id, 1234)) .andExpectAll( status().isNotFound, content().contentType(MediaType.APPLICATION_JSON), @@ -1506,7 +1506,7 @@ internal class ProjectControllerTest @Autowired constructor( } @NestedTest - @DisplayName("DELETE /projects/{idProject}/hallOfFameMember/{idAccount}") + @DisplayName("DELETE /projects/{idProject}/hallOfFame/{idAccount}") inner class RemoveHallOfFameMember { @BeforeEach fun addToRepositories() { @@ -1525,7 +1525,7 @@ internal class ProjectControllerTest @Autowired constructor( @Test fun `should remove an account from project's hall of fame`() { mockMvc.perform( - delete("/projects/{idProject}/hallOfFameMember/{idAccount}", testProject.id, testAccount2.id) + delete("/projects/{idProject}/hallOfFame/{idAccount}", testProject.id, testAccount2.id) ) .andExpectAll( status().isOk, @@ -1542,7 +1542,7 @@ internal class ProjectControllerTest @Autowired constructor( @Test fun `should fail if the account does not exist`() { - mockMvc.perform(delete("/projects/{idProject}/hallOfFameMember/{idAccount}", testProject.id, 1234)) + mockMvc.perform(delete("/projects/{idProject}/hallOfFame/{idAccount}", testProject.id, 1234)) .andExpectAll( status().isNotFound, content().contentType(MediaType.APPLICATION_JSON),