refactored to make use of the ServiceType enum throughout backend and frontend for typesafety

This commit is contained in:
2026-06-24 23:25:18 +02:00
parent 38c72d1bb8
commit fb64a7787f
24 changed files with 278 additions and 263 deletions
@@ -1,11 +1,13 @@
package com.vaessl.app;
import com.vaessl.app.connection.ServiceType;
public final class Mockdata {
private Mockdata() {}
public static final String MOCK_URL = "http://localhost:1234";
public static final String MOCK_SERVICE_TYPE = "SERVICE_TYPE";
public static final ServiceType MOCK_SERVICE_TYPE = ServiceType.HOMEBOX;
public static final String MOCK_USER = "user";
public static final String MOCK_PASS = "pw";
public static final String MOCK_ID = "item-1";
@@ -32,6 +32,7 @@ class ConnectionControllerTest {
private static final String LOGIN_PATH = LOGIN.getValue();
private static final String STATUS_PATH = CONNECTION_STATUS.getValue();
private static final String LOGOUT_PATH = "/connections/HOMEBOX";
private static final String SESSION = "SESSION";
private static final String VALID_HOMEBOX_RESPONSE = """
{
@@ -66,11 +67,11 @@ class ConnectionControllerTest {
.content(connectionRequestBody(wm)))
.andExpect(status().isOk()).andReturn();
Cookie sessionCookie = loginResult.getResponse().getCookie("SESSION");
Cookie sessionCookie = loginResult.getResponse().getCookie(SESSION);
mockMvc.perform(get(STATUS_PATH).cookie(sessionCookie)).andExpect(status().isOk())
.andExpect(jsonPath("$.length()").value(1))
.andExpect(jsonPath("$[0].serviceType").value(HOMEBOX.getValue()))
.andExpect(jsonPath("$[0].serviceType").value(HOMEBOX.name()))
.andExpect(jsonPath("$[0].username").value(MOCK_USER))
.andExpect(jsonPath("$[0].appUrl").value(wm.getHttpBaseUrl()))
.andExpect(jsonPath("$[0].connected").value(true));
@@ -87,10 +88,10 @@ class ConnectionControllerTest {
.content(connectionRequestBody(wm)))
.andExpect(status().isOk()).andReturn();
Cookie sessionCookie = loginResult.getResponse().getCookie("SESSION");
Cookie sessionCookie = loginResult.getResponse().getCookie(SESSION);
mockMvc.perform(get(STATUS_PATH).cookie(sessionCookie)).andExpect(status().isOk())
.andExpect(jsonPath("$[0].serviceType").value(HOMEBOX.getValue()))
.andExpect(jsonPath("$[0].serviceType").value(HOMEBOX.name()))
.andExpect(jsonPath("$[0].connected").value(false))
.andExpect(jsonPath("$[0].expiresAt")
.value("2000-01-01T00:00:00Z"));
@@ -106,7 +107,7 @@ class ConnectionControllerTest {
.content(connectionRequestBody(wm)))
.andExpect(status().isOk()).andReturn();
Cookie sessionCookie = loginResult.getResponse().getCookie("SESSION");
Cookie sessionCookie = loginResult.getResponse().getCookie(SESSION);
mockMvc.perform(delete(LOGOUT_PATH).cookie(sessionCookie))
.andExpect(status().isNoContent());
@@ -122,7 +123,7 @@ class ConnectionControllerTest {
.content(connectionRequestBody(wm)))
.andExpect(status().isOk()).andReturn();
Cookie sessionCookie = loginResult.getResponse().getCookie("SESSION");
Cookie sessionCookie = loginResult.getResponse().getCookie(SESSION);
// Verify connected before logout
mockMvc.perform(get(STATUS_PATH).cookie(sessionCookie)).andExpect(status().isOk())
@@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test;
import com.vaessl.app.exception.EmptyCredentialsException;
import static com.vaessl.app.Mockdata.*;
import static com.vaessl.app.connection.ServiceType.HOMEBOX;
import static org.assertj.core.api.Assertions.assertThat;
class HomeboxConnectionProviderTest {
@@ -13,8 +14,9 @@ class HomeboxConnectionProviderTest {
private final HomeboxConnectionProvider provider = new HomeboxConnectionProvider(null, null);
@Test
void checkCredentials_ShouldThrowException_WhenFieldsAreMissing() {
ConnectionRequest request = new ConnectionRequest(MOCK_URL, "HOMEBOX", null, null, false);
void checkCredentialsShouldThrowExceptionWhenFieldsAreMissing() {
ConnectionRequest request =
new ConnectionRequest(MOCK_URL, HOMEBOX, null, null, false);
EmptyCredentialsException exception = assertThrows(EmptyCredentialsException.class, () -> {
provider.checkCredentials(request);
@@ -6,7 +6,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.resttestclient.TestRestTemplate;
import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import com.github.tomakehurst.wiremock.http.Fault;
@@ -28,175 +32,182 @@ import static com.vaessl.app.connection.ServiceType.*;
@WireMockTest
class HomeboxIntegrationTest {
@Autowired
TestRestTemplate restTemplate;
@Autowired
TestRestTemplate restTemplate;
@Autowired
ConnectionRepository cRepository;
@Autowired
ConnectionRepository cRepository;
String okJsonHomeboxResponse = """
{
"token": "fake-jwt-token",
"attachmentToken": "fake-attach",
"expiresAt": "2026-04-26T02:23:13Z"
}
""";
String okJsonHomeboxResponse = """
{
"token": "fake-jwt-token",
"attachmentToken": "fake-attach",
"expiresAt": "2026-04-26T02:23:13Z"
}
""";
/**
* Returns Token and status code OK when login is successful.
*
* @param wm the WiremockRuntimeInfo object
*/
@Test
void shouldReturnStatusOkWhenHomeboxCredentialsAreValid(WireMockRuntimeInfo wm) {
/**
* Returns Token and status code OK when login is successful.
*
* @param wm the WiremockRuntimeInfo object
*/
@Test
void shouldReturnStatusOkWhenHomeboxCredentialsAreValid(WireMockRuntimeInfo wm) {
stubFor(post(HOMEBOX_LOGIN.getValue()).willReturn(okJson(okJsonHomeboxResponse)));
stubFor(post(HOMEBOX_LOGIN.getValue()).willReturn(okJson(okJsonHomeboxResponse)));
ResponseEntity<String> response =
restTemplate.postForEntity(LOGIN.getValue(), connectionRequest(wm), String.class);
ResponseEntity<String> response = restTemplate.postForEntity(LOGIN.getValue(),
connectionRequest(wm), String.class);
DocumentContext documentContext = JsonPath.parse(response.getBody());
DocumentContext documentContext = JsonPath.parse(response.getBody());
String serviceType = documentContext.read("$.serviceType");
assertThat(serviceType).isEqualTo("HOMEBOX");
String serviceType = documentContext.read("$.serviceType");
assertThat(serviceType).isEqualTo("HOMEBOX");
String expiresAt = documentContext.read("$.expiresAt", String.class);
assertThat(expiresAt).isEqualTo("2026-04-26T02:23:13Z");
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
/**
* Tests the Unauthorized custom exception.
*
* @param wm the WiremockRuntimeInfo object
*/
@Test
void shouldFailToConnectWhenHomeboxReturnsUnauthorized(WireMockRuntimeInfo wm) {
stubFor(post(HOMEBOX_LOGIN.getValue()).willReturn(unauthorized()));
ResponseEntity<String> response =
restTemplate.postForEntity(LOGIN.getValue(), connectionRequest(wm), String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
assertThat(response.getBody()).contains(UNAUTHORIZED_WRONG_LOGIN.getMessage());
}
/**
* Tests a server error from the external api.
*
* @param wm the WiremockRuntimeInfo object
*/
@Test
void shouldFailToConnectWhenHomeboxReturnsServiceUnavailable(WireMockRuntimeInfo wm) {
stubFor(post(HOMEBOX_LOGIN.getValue()).willReturn(serviceUnavailable()));
ResponseEntity<String> response =
restTemplate.postForEntity(LOGIN.getValue(), connectionRequest(wm), String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);
assertThat(response.getBody()).contains(SERVER_ERROR_GENERAL.getMessage());
}
/**
* Checks when the service is unavailable or the app URL is wrong.
*/
@Test
void shouldReturnServiceUnavailableWhenHomeboxUrlIsWrong(WireMockRuntimeInfo wm) {
stubFor(post(HOMEBOX_LOGIN.getValue())
.willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER)));
ConnectionRequest badRequest = connectionRequest(wm);
ResponseEntity<String> response =
restTemplate.postForEntity(LOGIN.getValue(), badRequest, String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);
assertThat(response.getBody()).contains(SERVICE_UNAVAILABLE_UNREACHABLE_URL.getMessage());
}
/**
* Checks if any login fields are empty since all of them are mandatory.
*/
@Test
void shouldReturnBadRequestWhenHomeboxFieldsAreEmpty() {
ConnectionRequest emtpyRequest = new ConnectionRequest("", "", "", "", false);
ResponseEntity<String> response =
restTemplate.postForEntity(LOGIN.getValue(), emtpyRequest, String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
assertThat(response.getBody()).contains(BAD_REQUEST_EMPTY_FIELDS.getMessage());
}
/**
* Test the exception when there is an input for serviceType but it's unsupported.
*/
@Test
void shouldReturnWrongServiceTypeException(WireMockRuntimeInfo wm) {
ConnectionRequest wrongServiceTypeReq = new ConnectionRequest(wm.getHttpBaseUrl(),
"wrong-service-type", MOCK_USER, MOCK_PASS, false);
ResponseEntity<String> response =
restTemplate.postForEntity(LOGIN.getValue(), wrongServiceTypeReq, String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
assertThat(response.getBody()).contains(WRONG_SERVICE_TYPE.getMessage());
}
/**
* Tests the succesfull persistance of Homebox credential response to the database.
*
* @param wm the WiremockRuntimeInfo object
*/
@Test
void shouldSaveHomeboxConnectionResponseToDb(WireMockRuntimeInfo wm) {
stubFor(post(urlEqualTo(HOMEBOX_LOGIN.getValue()))
.willReturn(okJson(okJsonHomeboxResponse)));
ConnectionRequest request = connectionRequest(wm);
ResponseEntity<String> response =
restTemplate.postForEntity(LOGIN.getValue(), request, String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
ConnectionEntity dbEntry =
cRepository.findByAppUrlAndUsername(request.appUrl(), request.username());
assertThat(dbEntry).isNotNull();
assertThat(dbEntry.getAppUrl()).isEqualTo(request.appUrl());
assertThat(dbEntry.getUsername()).isEqualTo(request.username());
if (dbEntry instanceof HomeboxEntity hbE) {
assertThat(hbE.getToken()).isEqualTo("fake-jwt-token");
assertThat(hbE.getAttachmentToken()).isEqualTo("fake-attach");
assertThat(hbE.getExpiresAt().toString()).hasToString("2026-04-26T02:23:13Z");
String expiresAt = documentContext.read("$.expiresAt", String.class);
assertThat(expiresAt).isEqualTo("2026-04-26T02:23:13Z");
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
}
@Test
void shouldReturnEmptyCredentialsExceptionWhenCredsAreMissing(WireMockRuntimeInfo wm) {
ConnectionRequest missingCredentials = new ConnectionRequest(wm.getHttpBaseUrl(),
HOMEBOX.getValue(), MOCK_USER, null, false);
/**
* Tests the Unauthorized custom exception.
*
* @param wm the WiremockRuntimeInfo object
*/
@Test
void shouldFailToConnectWhenHomeboxReturnsUnauthorized(WireMockRuntimeInfo wm) {
ResponseEntity<String> response =
restTemplate.postForEntity(LOGIN.getValue(), missingCredentials, String.class);
stubFor(post(HOMEBOX_LOGIN.getValue()).willReturn(unauthorized()));
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
assertThat(response.getBody()).contains(BAD_REQUEST_EMPTY_FIELDS.getMessage());
}
ResponseEntity<String> response = restTemplate.postForEntity(LOGIN.getValue(),
connectionRequest(wm), String.class);
/**
* Creates a valid connection request with a mock Api through WireMockRuntimeInfo.
*
* @param wm the WiremockRuntimeInfo object
* @return a mock api connection request.
*/
private ConnectionRequest connectionRequest(WireMockRuntimeInfo wm) {
return new ConnectionRequest(wm.getHttpBaseUrl(), HOMEBOX.getValue(), MOCK_USER, MOCK_PASS,
null);
}
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
assertThat(response.getBody()).contains(UNAUTHORIZED_WRONG_LOGIN.getMessage());
}
/**
* Tests a server error from the external api.
*
* @param wm the WiremockRuntimeInfo object
*/
@Test
void shouldFailToConnectWhenHomeboxReturnsServiceUnavailable(WireMockRuntimeInfo wm) {
stubFor(post(HOMEBOX_LOGIN.getValue()).willReturn(serviceUnavailable()));
ResponseEntity<String> response = restTemplate.postForEntity(LOGIN.getValue(),
connectionRequest(wm), String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);
assertThat(response.getBody()).contains(SERVER_ERROR_GENERAL.getMessage());
}
/**
* Checks when the service is unavailable or the app URL is wrong.
*/
@Test
void shouldReturnServiceUnavailableWhenHomeboxUrlIsWrong(WireMockRuntimeInfo wm) {
stubFor(post(HOMEBOX_LOGIN.getValue())
.willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER)));
ConnectionRequest badRequest = connectionRequest(wm);
ResponseEntity<String> response = restTemplate.postForEntity(LOGIN.getValue(),
badRequest, String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);
assertThat(response.getBody())
.contains(SERVICE_UNAVAILABLE_UNREACHABLE_URL.getMessage());
}
/**
* Checks if any login fields are empty since all of them are mandatory.
*/
@Test
void shouldReturnBadRequestWhenHomeboxFieldsAreEmpty() {
ConnectionRequest emtpyRequest = new ConnectionRequest("", null, "", "", false);
ResponseEntity<String> response = restTemplate.postForEntity(LOGIN.getValue(),
emtpyRequest, String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
assertThat(response.getBody()).contains(BAD_REQUEST_EMPTY_FIELDS.getMessage());
}
/**
* Test the exception when there is an input for serviceType but it's unsupported.
*/
@Test
void shouldReturnBadRequestWhenServiceTypeIsInvalid(WireMockRuntimeInfo wm) {
String body = """
{"appUrl":"%s","serviceType":"wrong-service-type","username":"%s","password":"%s"}
"""
.formatted(wm.getHttpBaseUrl(), MOCK_USER, MOCK_PASS);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(body, headers);
ResponseEntity<String> response = restTemplate.exchange(LOGIN.getValue(),
HttpMethod.POST, entity, String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
}
/**
* Tests the succesfull persistance of Homebox credential response to the database.
*
* @param wm the WiremockRuntimeInfo object
*/
@Test
void shouldSaveHomeboxConnectionResponseToDb(WireMockRuntimeInfo wm) {
stubFor(post(urlEqualTo(HOMEBOX_LOGIN.getValue()))
.willReturn(okJson(okJsonHomeboxResponse)));
ConnectionRequest request = connectionRequest(wm);
ResponseEntity<String> response =
restTemplate.postForEntity(LOGIN.getValue(), request, String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
ConnectionEntity dbEntry = cRepository.findByAppUrlAndUsername(request.appUrl(),
request.username());
assertThat(dbEntry).isNotNull();
assertThat(dbEntry.getAppUrl()).isEqualTo(request.appUrl());
assertThat(dbEntry.getUsername()).isEqualTo(request.username());
if (dbEntry instanceof HomeboxEntity hbE) {
assertThat(hbE.getToken()).isEqualTo("fake-jwt-token");
assertThat(hbE.getAttachmentToken()).isEqualTo("fake-attach");
assertThat(hbE.getExpiresAt().toString())
.hasToString("2026-04-26T02:23:13Z");
}
}
@Test
void shouldReturnEmptyCredentialsExceptionWhenCredsAreMissing(WireMockRuntimeInfo wm) {
ConnectionRequest missingCredentials = new ConnectionRequest(wm.getHttpBaseUrl(),
HOMEBOX, MOCK_USER, null, false);
ResponseEntity<String> response = restTemplate.postForEntity(LOGIN.getValue(),
missingCredentials, String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
assertThat(response.getBody()).contains(BAD_REQUEST_EMPTY_FIELDS.getMessage());
}
/**
* Creates a valid connection request with a mock Api through WireMockRuntimeInfo.
*
* @param wm the WiremockRuntimeInfo object
* @return a mock api connection request.
*/
private ConnectionRequest connectionRequest(WireMockRuntimeInfo wm) {
return new ConnectionRequest(wm.getHttpBaseUrl(), HOMEBOX, MOCK_USER, MOCK_PASS,
null);
}
}
@@ -12,6 +12,8 @@ import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import com.vaessl.app.connection.ConnectionRepository;
import com.vaessl.app.exception.ConnectionNotFoundException;
import static com.vaessl.app.connection.ServiceType.HOMEBOX;
@ExtendWith(MockitoExtension.class)
class HomeboxSearchProviderTest {
@@ -27,7 +29,7 @@ class HomeboxSearchProviderTest {
when(mockRepo.findByAppUrlAndUsername(MOCK_URL, MOCK_USER)).thenReturn(null);
SearchRequest request = new SearchRequest(MOCK_URL, MOCK_USER, "test query", "HOMEBOX");
SearchRequest request = new SearchRequest(MOCK_URL, MOCK_USER, "test query", HOMEBOX);
Pageable pageable = PageRequest.of(0, 10);
assertThrows(ConnectionNotFoundException.class,
() -> provider.getSearchResults(request, pageable));
@@ -110,25 +110,21 @@ class SearchControllerTest {
}
@Test
void shouldReturnUnauthorizedWhenSessionHasNoConnectionForRequestedServiceType(
WireMockRuntimeInfo wm) throws Exception {
WireMock.stubFor(WireMock.post(HOMEBOX_LOGIN.getValue())
.willReturn(WireMock.okJson(VALID_HOMEBOX_LOGIN_RESPONSE)));
MvcResult loginResult =
mockMvc.perform(post(LOGIN_PATH).contentType(MediaType.APPLICATION_JSON)
.content(connectionRequestBody(wm))).andExpect(status().isOk()).andReturn();
Cookie sessionCookie = loginResult.getResponse().getCookie("SESSION");
mockMvc.perform(
post(SEARCH_REQUEST).cookie(sessionCookie).contentType(MediaType.APPLICATION_JSON)
.content(searchRequestBody(wm, "OTHER_SERVICETYPE")))
.andExpect(status().isUnauthorized());
void shouldReturnBadRequestWhenServiceTypeIsInvalid() throws Exception {
mockMvc.perform(post(SEARCH_REQUEST).contentType(MediaType.APPLICATION_JSON)
.content(searchRequestBody("INVALID_SERVICETYPE")))
.andExpect(status().isBadRequest());
}
private String searchRequestBody(WireMockRuntimeInfo wm, String serviceType) {
return searchRequestBody(wm.getHttpBaseUrl(), serviceType);
}
private String searchRequestBody(String serviceType) {
return searchRequestBody("http://irrelevant", serviceType);
}
private String searchRequestBody(String appUrl, String serviceType) {
return """
{
"appUrl": "%s",
@@ -136,7 +132,7 @@ class SearchControllerTest {
"serviceType": "%s",
"username": "%s"
}
""".formatted(wm.getHttpBaseUrl(), serviceType, MOCK_USER);
""".formatted(appUrl, serviceType, MOCK_USER);
}
private String connectionRequestBody(WireMockRuntimeInfo wm) {