added SyncControllerTests
This commit is contained in:
@@ -2,7 +2,8 @@ package com.vaessl.app.shared;
|
|||||||
|
|
||||||
public enum Endpoint {
|
public enum Endpoint {
|
||||||
HOMEBOX_LOGIN("/api/v1/users/login"), LOGIN("/login"), CONNECTION_STATUS(
|
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;
|
private final String value;
|
||||||
|
|
||||||
|
|||||||
@@ -13,4 +13,43 @@ public final class Mockdata {
|
|||||||
public static final String MOCK_ID = "item-1";
|
public static final String MOCK_ID = "item-1";
|
||||||
public static final String MOCK_TITLE = "title";
|
public static final String MOCK_TITLE = "title";
|
||||||
public static final String MOCK_DESCRIPTION = "desc";
|
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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,14 @@ package com.vaessl.app.search;
|
|||||||
|
|
||||||
import static com.vaessl.app.Mockdata.MOCK_PASS;
|
import static com.vaessl.app.Mockdata.MOCK_PASS;
|
||||||
import static com.vaessl.app.Mockdata.MOCK_USER;
|
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.request.MockMvcRequestBuilders.*;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
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 com.github.tomakehurst.wiremock.junit5.WireMockTest;
|
||||||
import jakarta.servlet.http.Cookie;
|
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
|
@SpringBootTest
|
||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc
|
||||||
@WireMockTest
|
@WireMockTest
|
||||||
@@ -32,47 +44,14 @@ class SearchControllerTest {
|
|||||||
|
|
||||||
private static final String SEARCH_REQUEST = SEARCH.getValue();
|
private static final String SEARCH_REQUEST = SEARCH.getValue();
|
||||||
|
|
||||||
private static final String VALID_HOMEBOX_LOGIN_RESPONSE = """
|
/**
|
||||||
{
|
* Logs in against a stubbed Homebox instance, then performs a keyword search using
|
||||||
"token": "fake-bearer-token",
|
* the resulting session cookie. Expects {@code 200 OK} with the mocked item mapped
|
||||||
"attachmentToken": "fake-attach-token",
|
* into the paged response body.
|
||||||
"expiresAt": "2099-01-01T00:00:00Z"
|
*
|
||||||
}
|
* @param wm WireMock runtime info, injected by {@link WireMockTest}, used to point
|
||||||
""";
|
* the client at the stub server
|
||||||
|
*/
|
||||||
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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReturnListOfQueriedHomeboxItems(WireMockRuntimeInfo wm) throws Exception {
|
void shouldReturnListOfQueriedHomeboxItems(WireMockRuntimeInfo wm) throws Exception {
|
||||||
|
|
||||||
@@ -93,9 +72,14 @@ class SearchControllerTest {
|
|||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.content[0].title").value("MacBook Pro A1398"))
|
.andExpect(jsonPath("$.content[0].title").value("MacBook Pro A1398"))
|
||||||
.andExpect(jsonPath("$.totalElements").value(1))
|
.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
|
@Test
|
||||||
void shouldReturnUnauthorizedWhenNoSession() throws Exception {
|
void shouldReturnUnauthorizedWhenNoSession() throws Exception {
|
||||||
mockMvc.perform(post(SEARCH_REQUEST).contentType(MediaType.APPLICATION_JSON).content("""
|
mockMvc.perform(post(SEARCH_REQUEST).contentType(MediaType.APPLICATION_JSON).content("""
|
||||||
@@ -109,6 +93,10 @@ class SearchControllerTest {
|
|||||||
""")).andExpect(status().isUnauthorized());
|
""")).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
|
@Test
|
||||||
void shouldReturnBadRequestWhenServiceTypeIsInvalid() throws Exception {
|
void shouldReturnBadRequestWhenServiceTypeIsInvalid() throws Exception {
|
||||||
mockMvc.perform(post(SEARCH_REQUEST).contentType(MediaType.APPLICATION_JSON)
|
mockMvc.perform(post(SEARCH_REQUEST).contentType(MediaType.APPLICATION_JSON)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user