From 87108a9ebde66c28989eb2981bdafb1afbd2960e Mon Sep 17 00:00:00 2001 From: kasun Date: Sun, 19 Jul 2026 16:07:47 +0200 Subject: [PATCH] added SyncControllerTests --- .../java/com/vaessl/app/shared/Endpoint.java | 3 +- .../test/java/com/vaessl/app/Mockdata.java | 39 +++++++ .../app/search/SearchControllerTest.java | 74 ++++++------- .../vaessl/app/sync/SyncControllerTest.java | 103 ++++++++++++++++++ 4 files changed, 175 insertions(+), 44 deletions(-) create mode 100644 backend/src/test/java/com/vaessl/app/sync/SyncControllerTest.java diff --git a/backend/src/main/java/com/vaessl/app/shared/Endpoint.java b/backend/src/main/java/com/vaessl/app/shared/Endpoint.java index 26682e4..5211208 100644 --- a/backend/src/main/java/com/vaessl/app/shared/Endpoint.java +++ b/backend/src/main/java/com/vaessl/app/shared/Endpoint.java @@ -2,7 +2,8 @@ package com.vaessl.app.shared; public enum Endpoint { HOMEBOX_LOGIN("/api/v1/users/login"), LOGIN("/login"), CONNECTION_STATUS( - "/connections/status"), HOMEBOX_QUERY_ALL_ITEMS("/api/v1/entities"), SEARCH("/search"); + "/connections/status"), HOMEBOX_QUERY_ALL_ITEMS( + "/api/v1/entities"), SEARCH("/search"), SYNC("/sync"); private final String value; diff --git a/backend/src/test/java/com/vaessl/app/Mockdata.java b/backend/src/test/java/com/vaessl/app/Mockdata.java index 4ebf98b..4f200ad 100644 --- a/backend/src/test/java/com/vaessl/app/Mockdata.java +++ b/backend/src/test/java/com/vaessl/app/Mockdata.java @@ -13,4 +13,43 @@ public final class Mockdata { public static final String MOCK_ID = "item-1"; public static final String MOCK_TITLE = "title"; public static final String MOCK_DESCRIPTION = "desc"; + public static final String VALID_HOMEBOX_LOGIN_RESPONSE = """ + { + "token": "fake-bearer-token", + "attachmentToken": "fake-attach-token", + "expiresAt": "2099-01-01T00:00:00Z" + } + """; + public static final String VALID_HOMEBOX_ALL_ITEMS_QUERY_RESPONSE = """ + { + "page": -1, + "pageSize": -1, + "total": 1, + "items": [ + { + "id": "c643e7f9-93d0-4b5f-ae4d-e1c2d90389e0", + "assetId": "000-001", + "name": "MacBook Pro A1398", + "description": "Running Linux (Fedora)", + "quantity": 1, + "insured": false, + "archived": false, + "createdAt": "2026-05-13T19:52:20.016176Z", + "updatedAt": "2026-05-14T12:39:11.836403Z", + "purchasePrice": 0, + "parent": { + "id": "b6f60ab8-3a2a-4a8d-a4bf-897d0555f636", + "name": "Server Schrank Ikea weiß", + "description": "Weißer Ikea Schrank, wo sich der Server befindet.", + "createdAt": "2026-05-13T19:55:55.817576Z", + "updatedAt": "2026-05-14T12:37:24.396651Z" + }, + "tags": [], + "imageId": "cb3e44d5-ccd4-421e-9f5a-f52cd5f40ca6", + "thumbnailId": "2bfd53fa-1bf1-483c-8d76-7720464532fa", + "soldTime": "0001-01-01T00:00:00Z" + } + ] + } + """; } diff --git a/backend/src/test/java/com/vaessl/app/search/SearchControllerTest.java b/backend/src/test/java/com/vaessl/app/search/SearchControllerTest.java index 9e2a3e1..6ca73bb 100644 --- a/backend/src/test/java/com/vaessl/app/search/SearchControllerTest.java +++ b/backend/src/test/java/com/vaessl/app/search/SearchControllerTest.java @@ -2,7 +2,14 @@ package com.vaessl.app.search; import static com.vaessl.app.Mockdata.MOCK_PASS; import static com.vaessl.app.Mockdata.MOCK_USER; -import static com.vaessl.app.shared.Endpoint.*; +import static com.vaessl.app.Mockdata.VALID_HOMEBOX_ALL_ITEMS_QUERY_RESPONSE; +import static com.vaessl.app.Mockdata.VALID_HOMEBOX_LOGIN_RESPONSE; + +import static com.vaessl.app.shared.Endpoint.LOGIN; +import static com.vaessl.app.shared.Endpoint.SEARCH; +import static com.vaessl.app.shared.Endpoint.HOMEBOX_QUERY_ALL_ITEMS; +import static com.vaessl.app.shared.Endpoint.HOMEBOX_LOGIN; + import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -18,6 +25,11 @@ import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; import com.github.tomakehurst.wiremock.junit5.WireMockTest; import jakarta.servlet.http.Cookie; +/** + * Integration tests for {@code POST /api/search}, verifying the session-gated + * contract and request validation against a mocked Homebox backend (via WireMock) + * and a real Spring MVC dispatch chain (via {@link MockMvc}). + */ @SpringBootTest @AutoConfigureMockMvc @WireMockTest @@ -32,47 +44,14 @@ class SearchControllerTest { private static final String SEARCH_REQUEST = SEARCH.getValue(); - private static final String VALID_HOMEBOX_LOGIN_RESPONSE = """ - { - "token": "fake-bearer-token", - "attachmentToken": "fake-attach-token", - "expiresAt": "2099-01-01T00:00:00Z" - } - """; - - private static final String VALID_HOMEBOX_ALL_ITEMS_QUERY_RESPONSE = """ - { - "page": -1, - "pageSize": -1, - "total": 1, - "items": [ - { - "id": "c643e7f9-93d0-4b5f-ae4d-e1c2d90389e0", - "assetId": "000-001", - "name": "MacBook Pro A1398", - "description": "Running Linux (Fedora)", - "quantity": 1, - "insured": false, - "archived": false, - "createdAt": "2026-05-13T19:52:20.016176Z", - "updatedAt": "2026-05-14T12:39:11.836403Z", - "purchasePrice": 0, - "parent": { - "id": "b6f60ab8-3a2a-4a8d-a4bf-897d0555f636", - "name": "Server Schrank Ikea weiß", - "description": "Weißer Ikea Schrank, wo sich der Server befindet.", - "createdAt": "2026-05-13T19:55:55.817576Z", - "updatedAt": "2026-05-14T12:37:24.396651Z" - }, - "tags": [], - "imageId": "cb3e44d5-ccd4-421e-9f5a-f52cd5f40ca6", - "thumbnailId": "2bfd53fa-1bf1-483c-8d76-7720464532fa", - "soldTime": "0001-01-01T00:00:00Z" - } - ] - } - """; - + /** + * Logs in against a stubbed Homebox instance, then performs a keyword search using + * the resulting session cookie. Expects {@code 200 OK} with the mocked item mapped + * into the paged response body. + * + * @param wm WireMock runtime info, injected by {@link WireMockTest}, used to point + * the client at the stub server + */ @Test void shouldReturnListOfQueriedHomeboxItems(WireMockRuntimeInfo wm) throws Exception { @@ -93,9 +72,14 @@ class SearchControllerTest { .andExpect(status().isOk()) .andExpect(jsonPath("$.content[0].title").value("MacBook Pro A1398")) .andExpect(jsonPath("$.totalElements").value(1)) - .andExpect(jsonPath("$.content[0].extraData.locationName").value("Server Schrank Ikea weiß")); + .andExpect(jsonPath("$.content[0].extraData.locationName") + .value("Server Schrank Ikea weiß")); } + /** + * Calls {@code /api/search} with no session cookie attached and expects + * {@code 401 Unauthorized}, confirming the endpoint is session-gated. + */ @Test void shouldReturnUnauthorizedWhenNoSession() throws Exception { mockMvc.perform(post(SEARCH_REQUEST).contentType(MediaType.APPLICATION_JSON).content(""" @@ -109,6 +93,10 @@ class SearchControllerTest { """)).andExpect(status().isUnauthorized()); } + /** + * Sends a search request with a {@code serviceType} that doesn't map to any known + * {@link com.vaessl.app.shared.ServiceType} and expects {@code 400 Bad Request}. + */ @Test void shouldReturnBadRequestWhenServiceTypeIsInvalid() throws Exception { mockMvc.perform(post(SEARCH_REQUEST).contentType(MediaType.APPLICATION_JSON) diff --git a/backend/src/test/java/com/vaessl/app/sync/SyncControllerTest.java b/backend/src/test/java/com/vaessl/app/sync/SyncControllerTest.java new file mode 100644 index 0000000..05011e4 --- /dev/null +++ b/backend/src/test/java/com/vaessl/app/sync/SyncControllerTest.java @@ -0,0 +1,103 @@ +package com.vaessl.app.sync; + +import static com.vaessl.app.shared.Endpoint.HOMEBOX_LOGIN; +import static com.vaessl.app.shared.Endpoint.HOMEBOX_QUERY_ALL_ITEMS; +import static com.vaessl.app.shared.Endpoint.LOGIN; +import static com.vaessl.app.shared.Endpoint.SYNC; +import static com.vaessl.app.Mockdata.MOCK_USER; +import static com.vaessl.app.Mockdata.MOCK_SERVICE_TYPE; +import static com.vaessl.app.Mockdata.MOCK_PASS; +import static com.vaessl.app.Mockdata.VALID_HOMEBOX_ALL_ITEMS_QUERY_RESPONSE; +import static com.vaessl.app.Mockdata.VALID_HOMEBOX_LOGIN_RESPONSE; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import jakarta.servlet.http.Cookie; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +/** + * Integration tests for {@code POST /api/sync}, verifying the session-gated contract against a + * mocked Homebox backend (via WireMock) and a real Spring MVC dispatch chain (via {@link MockMvc}). + */ +@SpringBootTest +@AutoConfigureMockMvc +@WireMockTest +class SyncControllerTest { + + @Autowired + MockMvc mockMvc; + + private static final String SYNC_PATH = SYNC.getValue(); + + /** + * Logs in against a stubbed Homebox instance, then performs a sync using the resulting session + * cookie. Expects a {@code 204 No Content} response once the mocked item catalog has been paged + * through and embedded. + * + * @param wm WireMock runtime info, injected by {@link WireMockTest}, used to point the client + * at the stub server + */ + @Test + void shouldReturn204NoContentOnSuccessfulSync(WireMockRuntimeInfo wm) throws Exception { + + WireMock.stubFor(WireMock.post(HOMEBOX_LOGIN.getValue()) + .willReturn(WireMock.okJson(VALID_HOMEBOX_LOGIN_RESPONSE))); + + WireMock.stubFor(WireMock.get(WireMock.urlPathEqualTo(HOMEBOX_QUERY_ALL_ITEMS.getValue())) + .willReturn(WireMock.okJson(VALID_HOMEBOX_ALL_ITEMS_QUERY_RESPONSE))); + + MvcResult loginResult = + mockMvc.perform(post(LOGIN.getValue()).contentType(MediaType.APPLICATION_JSON) + .content(connectionRequestBody(wm))).andExpect(status().isOk()).andReturn(); + + Cookie sessionCookie = loginResult.getResponse().getCookie("SESSION"); + + mockMvc.perform(post(SYNC_PATH).cookie(sessionCookie) + .contentType(MediaType.APPLICATION_JSON).content(syncRequestBody(wm))) + .andExpect(status().isNoContent()); + } + + /** + * Calls {@code /api/sync} with no session cookie attached and expects {@code 401 Unauthorized}, + * confirming the endpoint is session-gated. + * + * @param wm WireMock runtime info, injected by {@link WireMockTest}, used only to build a + * well-formed request body + */ + @Test + void shouldReturn401UnauthorizedWhenSessionIsNull(WireMockRuntimeInfo wm) throws Exception { + mockMvc.perform(post(SYNC_PATH).contentType(MediaType.APPLICATION_JSON) + .content(syncRequestBody(wm))).andExpect(status().isUnauthorized()); + + } + + private String connectionRequestBody(WireMockRuntimeInfo wm) { + return """ + { + "appUrl": "%s", + "serviceType": "%s", + "username": "%s", + "password": "%s" + } + """.formatted(wm.getHttpBaseUrl(), MOCK_SERVICE_TYPE, MOCK_USER, MOCK_PASS); + } + + private String syncRequestBody(WireMockRuntimeInfo wm) { + return """ + { + "appUrl": "%s", + "serviceType": "%s", + "username": "%s" + } + """.formatted(wm.getHttpBaseUrl(), MOCK_SERVICE_TYPE, MOCK_USER); + } +}