added post request to achieve login response with tokens
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
package com.vaessl.app.connection;
|
||||||
|
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.vaessl.app.dto.ConnectionRequest;
|
||||||
|
import com.vaessl.app.dto.ConnectionResponse;
|
||||||
|
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class ConnectionController {
|
||||||
|
|
||||||
|
private final ConnectionService connectionService;
|
||||||
|
|
||||||
|
public ConnectionController(ConnectionService connectionService) {
|
||||||
|
this.connectionService = connectionService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/login")
|
||||||
|
public ResponseEntity<ConnectionResponse> loginResponse(@Valid @RequestBody ConnectionRequest request) {
|
||||||
|
ConnectionResponse connectionResponse = connectionService.login(request);
|
||||||
|
return ResponseEntity.ok(connectionResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.vaessl.app.connection;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.client.RestClient;
|
||||||
|
|
||||||
|
import com.vaessl.app.dto.ConnectionRequest;
|
||||||
|
import com.vaessl.app.dto.ConnectionResponse;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ConnectionService {
|
||||||
|
|
||||||
|
private final RestClient.Builder restClientBuilder;
|
||||||
|
|
||||||
|
public ConnectionService(RestClient.Builder restClientBuilder) {
|
||||||
|
this.restClientBuilder = restClientBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectionResponse login(ConnectionRequest request) {
|
||||||
|
//TODO: Look into Map<String, RestClient> to cache restclient requests.
|
||||||
|
return restClientBuilder.baseUrl(request.appUrl())
|
||||||
|
.build()
|
||||||
|
.post()
|
||||||
|
.uri("/api/v1/users/login")
|
||||||
|
.body(request)
|
||||||
|
.retrieve()
|
||||||
|
.body(ConnectionResponse.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.vaessl.app.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
public record ConnectionRequest(
|
||||||
|
@NotBlank(message = "App URL is mandatory") String appUrl,
|
||||||
|
@NotBlank(message = "Username is mandatory") String username,
|
||||||
|
@NotBlank(message = "Password is mandatory") String password) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.vaessl.app.dto;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
public record ConnectionResponse(String token, String attachmentToken, Instant expiresAt) {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.vaessl.app.exception;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ProblemDetail;
|
||||||
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
import org.springframework.web.client.HttpClientErrorException;
|
||||||
|
import org.springframework.web.client.HttpServerErrorException;
|
||||||
|
import org.springframework.web.client.ResourceAccessException;
|
||||||
|
|
||||||
|
@RestControllerAdvice
|
||||||
|
public class GlobalExceptionHandler {
|
||||||
|
|
||||||
|
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||||
|
public ProblemDetail handleEmptyCredentialInput(MethodArgumentNotValidException e) {
|
||||||
|
return ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "Fields must not be empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(HttpClientErrorException.Unauthorized.class)
|
||||||
|
public ProblemDetail handleUnauthorizedAccess(HttpClientErrorException e) {
|
||||||
|
|
||||||
|
return ProblemDetail.forStatusAndDetail(HttpStatus.UNAUTHORIZED, "Invalid username or password.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(ResourceAccessException.class)
|
||||||
|
public ProblemDetail handleNoConnection(ResourceAccessException e) {
|
||||||
|
|
||||||
|
return ProblemDetail.forStatusAndDetail(HttpStatus.SERVICE_UNAVAILABLE, "The target URL is unreachable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(HttpServerErrorException.class)
|
||||||
|
public ProblemDetail handleTimeoutOrNotFound(HttpServerErrorException e) {
|
||||||
|
|
||||||
|
return ProblemDetail
|
||||||
|
.forStatusAndDetail(e.getStatusCode(),
|
||||||
|
"The external app returned a server error: " + e.getStatusText());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package com.vaessl.app.connection;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
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.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
||||||
|
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
|
||||||
|
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
|
||||||
|
|
||||||
|
import com.jayway.jsonpath.DocumentContext;
|
||||||
|
import com.jayway.jsonpath.JsonPath;
|
||||||
|
import com.vaessl.app.dto.ConnectionRequest;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
||||||
|
|
||||||
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||||
|
@AutoConfigureTestRestTemplate
|
||||||
|
@WireMockTest
|
||||||
|
public class ConnectionIntegrationTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
TestRestTemplate restTemplate;
|
||||||
|
|
||||||
|
private static final String API_LOGIN = "/api/v1/users/login";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Token and status code OK when login is successful.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void shouldReturnTokenAndStatusOkWhenCredentialsAreValid(WireMockRuntimeInfo wm) {
|
||||||
|
|
||||||
|
stubFor(post(API_LOGIN)
|
||||||
|
.willReturn(okJson("""
|
||||||
|
{
|
||||||
|
"token": "fake-jwt-token",
|
||||||
|
"attachmentToken": "fake-attach",
|
||||||
|
"expiresAt": "2026-04-26T02:23:13Z"
|
||||||
|
}
|
||||||
|
""")));
|
||||||
|
|
||||||
|
ResponseEntity<String> response = restTemplate.postForEntity("/login", connectionRequest(wm), String.class);
|
||||||
|
|
||||||
|
DocumentContext documentContext = JsonPath.parse(response.getBody());
|
||||||
|
|
||||||
|
String token = documentContext.read("$.token");
|
||||||
|
assertThat(token).isEqualTo("fake-jwt-token");
|
||||||
|
|
||||||
|
String attachmentToken = documentContext.read("$.attachmentToken");
|
||||||
|
assertThat(attachmentToken).isEqualTo("fake-attach");
|
||||||
|
|
||||||
|
String expiresAt = documentContext.read("$.expiresAt", String.class);
|
||||||
|
assertThat(expiresAt).isEqualTo("2026-04-26T02:23:13Z");
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if login request fails with 401 unauthorized.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void shouldFailToConnectWhenHomeboxReturnsUnauthorized(WireMockRuntimeInfo wm) {
|
||||||
|
|
||||||
|
stubFor(post(API_LOGIN).willReturn(unauthorized()));
|
||||||
|
|
||||||
|
ResponseEntity<String> response = restTemplate.postForEntity("/login", connectionRequest(wm), String.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
|
||||||
|
assertThat(response.getBody()).contains("Invalid username or password.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests a server error from the external api.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void shouldFailToConnectWhenHomeboxReturnsServiceUnavailable(WireMockRuntimeInfo wm) {
|
||||||
|
|
||||||
|
stubFor(post(API_LOGIN).willReturn(serviceUnavailable()));
|
||||||
|
|
||||||
|
ResponseEntity<String> response = restTemplate.postForEntity("/login", connectionRequest(wm), String.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);
|
||||||
|
assertThat(response.getBody()).contains("The external app returned a server error");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks when the service is unavailable or the app URL is wrong.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void shouldReturnServiceUnavailableWhenUrlIsCompletelyWrong() {
|
||||||
|
|
||||||
|
ConnectionRequest badRequest = new ConnectionRequest(
|
||||||
|
"http://localhost:1234",
|
||||||
|
"user",
|
||||||
|
"pass");
|
||||||
|
|
||||||
|
ResponseEntity<String> response = restTemplate.postForEntity("/login", badRequest, String.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);
|
||||||
|
assertThat(response.getBody()).contains("The target URL is unreachable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnBadRequestWhenUrlIsMissing() {
|
||||||
|
|
||||||
|
ConnectionRequest emtpyRequest = new ConnectionRequest("", "", "");
|
||||||
|
|
||||||
|
ResponseEntity<String> response = restTemplate.postForEntity("/login", emtpyRequest, String.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||||
|
assertThat(response.getBody()).contains("Fields must not be empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConnectionRequest connectionRequest(WireMockRuntimeInfo wireMockRuntimeInfo) {
|
||||||
|
return new ConnectionRequest(wireMockRuntimeInfo.getHttpBaseUrl(), "username", "password");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user