Skip to content

Commit

Permalink
split controller
Browse files Browse the repository at this point in the history
  • Loading branch information
mucsi96 committed Jul 14, 2023
1 parent 4801595 commit d73b5b2
Show file tree
Hide file tree
Showing 10 changed files with 299 additions and 253 deletions.
4 changes: 1 addition & 3 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
- Add test for access token refresh
- Add test for sync forbidden
- Split Weight controller into Weigth controller and withings controller
- Adjust the UI
- Create separate E2E test project which locally tests agains running angular + Spring API using Selnium. On CI it uses test containers of both images. Adding authontication headers with own server.
49 changes: 0 additions & 49 deletions main.tf

This file was deleted.

3 changes: 1 addition & 2 deletions server/src/main/java/mucsi96/traininglog/weight/Weight.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
Expand All @@ -18,7 +17,7 @@
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor(access= AccessLevel.PRIVATE, force=true)
@NoArgsConstructor
public class Weight {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,22 @@
package mucsi96.traininglog.weight;

import org.springframework.http.MediaType;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import io.swagger.v3.oas.annotations.Parameter;
import jakarta.annotation.security.RolesAllowed;
import lombok.RequiredArgsConstructor;
import mucsi96.traininglog.withings.WithingsService;
import mucsi96.traininglog.withings.oauth.WithingsClient;

@RestController
@RequestMapping(value = "/weight", produces = MediaType.APPLICATION_JSON_VALUE)
@RequiredArgsConstructor
@RolesAllowed("user")
public class WeightController {

private final WithingsService withingsService;
private final WeightService weightService;

@PostMapping("/pull-from-withings")
void sync(
@Parameter(hidden = true) @RegisteredOAuth2AuthorizedClient(WithingsClient.id) OAuth2AuthorizedClient withingsAuthorizedClient) {

if (!weightService.getTodayWeight().isPresent()) {
withingsService.getTodayWeight(withingsAuthorizedClient).ifPresent(weightService::saveWeight);
}
}

@GetMapping
@ResponseBody
WeightResponse weight() {
Double weight = weightService.getTodayWeight().map(Weight::getValue).orElse(null);
return WeightResponse
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package mucsi96.traininglog.withings;

import org.springframework.http.MediaType;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import io.swagger.v3.oas.annotations.Parameter;
import jakarta.annotation.security.RolesAllowed;
import lombok.RequiredArgsConstructor;
import mucsi96.traininglog.weight.WeightService;
import mucsi96.traininglog.withings.oauth.WithingsClient;

@RestController
@RequestMapping(value = "/withings", produces = MediaType.APPLICATION_JSON_VALUE)
@RequiredArgsConstructor
@RolesAllowed("user")
public class WithingsController {

private final WithingsService withingsService;
private final WeightService weightService;

@PostMapping("/sync")
void sync(
@Parameter(hidden = true) @RegisteredOAuth2AuthorizedClient(WithingsClient.id) OAuth2AuthorizedClient withingsAuthorizedClient) {

if (!weightService.getTodayWeight().isPresent()) {
withingsService.getTodayWeight(withingsAuthorizedClient).ifPresent(weightService::saveWeight);
}
}
}
1 change: 0 additions & 1 deletion server/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
server:
forward-headers-strategy: framework
shutdown: graceful
management:
endpoint:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import org.testcontainers.containers.PostgreSQLContainer;

@ActiveProfiles("test")
@SpringBootTest
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class BaseIntegrationTest {

Expand Down
177 changes: 0 additions & 177 deletions server/src/test/java/mucsi96/traininglog/WeightControllerTests.java
Original file line number Diff line number Diff line change
@@ -1,105 +1,34 @@
package mucsi96.traininglog;

import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;

import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneOffset;
import java.util.List;
import java.util.Optional;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.TestConstructor;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
import com.github.tomakehurst.wiremock.verification.LoggedRequest;
import com.jayway.jsonpath.JsonPath;

import lombok.RequiredArgsConstructor;
import mucsi96.traininglog.model.TestAuthorizedClient;
import mucsi96.traininglog.repository.TestAuthorizedClientRepository;
import mucsi96.traininglog.weight.Weight;
import mucsi96.traininglog.weight.WeightRepository;
import mucsi96.traininglog.withings.oauth.WithingsClient;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@RequiredArgsConstructor
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
public class WeightControllerTests extends BaseIntegrationTest {

private final MockMvc mockMvc;
private final TestAuthorizedClientRepository authorizedClientRepository;
private final WeightRepository weightRepository;

@LocalServerPort
private int port;

@RegisterExtension
static WireMockExtension mockWithingsServer = WireMockExtension.newInstance()
.options(wireMockConfig().dynamicPort())
.build();

@DynamicPropertySource
static void overrideProperties(DynamicPropertyRegistry registry) {

registry.add("spring.security.oauth2.client.provider.withings.authorization-uri",
() -> mockWithingsServer.baseUrl() + "/oauth2_user/authorize2");
registry.add(
"spring.security.oauth2.client.provider.withings.token-uri",
() -> mockWithingsServer.baseUrl() + "/v2/oauth2");

registry.add("spring.security.oauth2.client.registration.withings-client.client-id",
() -> "test-withings-client-id");
registry.add("spring.security.oauth2.client.registration.withings-client.client-secret",
() -> "test-withings-client-secret");
registry.add("withings.api.uri", () -> mockWithingsServer.baseUrl());
}

@AfterEach
void afterEach() {
authorizedClientRepository.deleteAll();
weightRepository.deleteAll();
}

private void authorizeWithingsOAuth2Client() {
TestAuthorizedClient authorizedClient = TestAuthorizedClient.builder()
.clientRegistrationId("withings-client")
.principalName("rob")
.accessTokenType("Bearer")
.accessTokenValue("test-access-token".getBytes(StandardCharsets.UTF_8))
.accessTokenIssuedAt(LocalDateTime.now())
.accessTokenExpiresAt(LocalDateTime.now().plusDays(1))
.accessTokenScopes("user.metrics")
.refreshTokenValue("test-refresh-token".getBytes(StandardCharsets.UTF_8))
.refreshTokenIssuedAt(LocalDateTime.now())
.build();

authorizedClientRepository.save(authorizedClient);
}

@Test
public void returns_not_authorized_if_no_preauth_headers_are_sent() throws Exception {
MockHttpServletResponse response = mockMvc
Expand Down Expand Up @@ -137,110 +66,4 @@ public void returns_weight_from_database() throws Exception {

assertThat(JsonPath.parse(response.getContentAsString()).read("$.weight", Double.class)).isEqualTo(83.5);
}

@Test
public void returns_not_authorized_if_authorized_client_is_not_found() throws Exception {
MockHttpServletResponse response = mockMvc
.perform(
post("/weight/pull-from-withings")
.headers(getAuthHeaders("user")))
.andReturn().getResponse();

assertThat(response.getStatus()).isEqualTo(401);
assertThat(JsonPath.parse(response.getContentAsString()).read("$._links.oauth2Login.href", String.class))
.isEqualTo("http://localhost/oauth2/authorization/withings-client");
}

@Test
public void redirects_to_withings_request_authorization_page() throws Exception {
MockHttpServletResponse response = mockMvc
.perform(
get("/oauth2/authorization/withings-client"))
.andReturn().getResponse();

assertThat(response.getStatus()).isEqualTo(302);
URI redirectUrl = new URI(response.getRedirectedUrl());
assertThat(redirectUrl).hasHost("localhost");
assertThat(redirectUrl).hasPort(mockWithingsServer.getPort());
assertThat(redirectUrl).hasPath("/oauth2_user/authorize2");
assertThat(redirectUrl).hasParameter(OAuth2ParameterNames.RESPONSE_TYPE, "code");
assertThat(redirectUrl).hasParameter(OAuth2ParameterNames.CLIENT_ID, "test-withings-client-id");
assertThat(redirectUrl).hasParameter(OAuth2ParameterNames.SCOPE, "user.metrics");
assertThat(redirectUrl).hasParameter(OAuth2ParameterNames.STATE);
assertThat(redirectUrl).hasParameter(OAuth2ParameterNames.REDIRECT_URI,
"http://localhost/authorize/oauth2/code/withings-client");
}

@Test
public void requests_withings_access_token_after_consent_is_granted() throws Exception {
mockWithingsServer.stubFor(WireMock.post("/v2/oauth2").willReturn(
WireMock.aResponse()
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.withBodyFile("withings-authorize.json")));

MockHttpSession mockHttpSession = new MockHttpSession();
MockHttpServletResponse response1 = mockMvc.perform(
get("/oauth2/authorization/withings-client")
.headers(getAuthHeaders("user"))
.session(mockHttpSession))
.andReturn().getResponse();
UriComponents components = UriComponentsBuilder.fromUriString(response1.getRedirectedUrl()).build();
String state = URLDecoder.decode(
components.getQueryParams().getFirst(OAuth2ParameterNames.STATE),
StandardCharsets.UTF_8);

MockHttpServletResponse response2 = mockMvc.perform(get("/authorize/oauth2/code/withings-client")
.headers(getAuthHeaders("user"))
.queryParam(OAuth2ParameterNames.STATE, state)
.queryParam(OAuth2ParameterNames.CODE, "test-authorization-code")
.session(mockHttpSession))
.andReturn().getResponse();

assertThat(response2.getStatus()).isEqualTo(302);
assertThat(response2.getRedirectedUrl()).isEqualTo("http://localhost/");

List<LoggedRequest> requests = mockWithingsServer
.findAll(WireMock.postRequestedFor(WireMock.urlEqualTo("/v2/oauth2")));
assertThat(requests).hasSize(1);
URI uri = new URI("?" + requests.get(0).getBodyAsString());

Optional<TestAuthorizedClient> authorizedClient = authorizedClientRepository.findById(WithingsClient.id);

assertThat(authorizedClient.isPresent()).isTrue();
assertThat(authorizedClient.get().getPrincipalName()).isEqualTo("rob");
assertThat(new String(authorizedClient.get().getAccessTokenValue(), StandardCharsets.UTF_8))
.isEqualTo("test-access-token");
assertThat(new String(authorizedClient.get().getRefreshTokenValue(), StandardCharsets.UTF_8))
.isEqualTo("test-refresh-token");
assertThat(uri).hasParameter(OAuth2ParameterNames.GRANT_TYPE, "authorization_code");
assertThat(uri).hasParameter(OAuth2ParameterNames.CODE, "test-authorization-code");
assertThat(uri).hasParameter("action", "requesttoken");
assertThat(uri).hasParameter(OAuth2ParameterNames.CLIENT_ID, "test-withings-client-id");
assertThat(uri).hasParameter(OAuth2ParameterNames.CLIENT_SECRET, "test-withings-client-secret");
}

@Test
public void pulls_todays_weight_from_withings_to_database() throws Exception {
authorizeWithingsOAuth2Client();
long startTime = LocalDateTime.of(LocalDate.now(), LocalTime.MIN).toInstant(ZoneOffset.UTC).getEpochSecond();
long endTime = LocalDateTime.of(LocalDate.now(), LocalTime.MAX).toInstant(ZoneOffset.UTC).getEpochSecond();
mockWithingsServer.stubFor(WireMock
.post(String.format("/measure?action=getmeas&meastype=1&category=1&startdate=%s&enddate=%s",
startTime, endTime))
.willReturn(
WireMock.aResponse()
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.withBodyFile("withings-measure.json")));
MockHttpServletResponse response = mockMvc
.perform(
post("/weight/pull-from-withings")
.headers(getAuthHeaders("user")))
.andReturn().getResponse();

assertThat(response.getStatus()).isEqualTo(200);
Optional<Weight> weight = weightRepository.findAll().stream().findFirst();
assertThat(weight.isPresent()).isTrue();
assertThat(weight.get().getValue()).isEqualTo(65.75);
assertThat(weight.get().getCreatedAt().getEpochSecond()).isEqualTo(1594245600L);
}
}
Loading

0 comments on commit d73b5b2

Please sign in to comment.