Skip to content

Commit d73b5b2

Browse files
committed
split controller
1 parent 4801595 commit d73b5b2

File tree

10 files changed

+299
-253
lines changed

10 files changed

+299
-253
lines changed

TODO.md

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,2 @@
1-
- Add test for access token refresh
2-
- Add test for sync forbidden
3-
- Split Weight controller into Weigth controller and withings controller
41
- Adjust the UI
2+
- 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.

main.tf

-49
This file was deleted.

server/src/main/java/mucsi96/traininglog/weight/Weight.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import jakarta.persistence.GeneratedValue;
88
import jakarta.persistence.GenerationType;
99
import jakarta.persistence.Id;
10-
import lombok.AccessLevel;
1110
import lombok.AllArgsConstructor;
1211
import lombok.Builder;
1312
import lombok.Data;
@@ -18,7 +17,7 @@
1817
@Entity
1918
@Builder
2019
@AllArgsConstructor
21-
@NoArgsConstructor(access= AccessLevel.PRIVATE, force=true)
20+
@NoArgsConstructor
2221
public class Weight {
2322
@Id
2423
@GeneratedValue(strategy = GenerationType.IDENTITY)

server/src/main/java/mucsi96/traininglog/weight/WeightController.java

-18
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,22 @@
11
package mucsi96.traininglog.weight;
22

33
import org.springframework.http.MediaType;
4-
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
5-
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
64
import org.springframework.web.bind.annotation.GetMapping;
7-
import org.springframework.web.bind.annotation.PostMapping;
85
import org.springframework.web.bind.annotation.RequestMapping;
9-
import org.springframework.web.bind.annotation.ResponseBody;
106
import org.springframework.web.bind.annotation.RestController;
117

12-
import io.swagger.v3.oas.annotations.Parameter;
138
import jakarta.annotation.security.RolesAllowed;
149
import lombok.RequiredArgsConstructor;
15-
import mucsi96.traininglog.withings.WithingsService;
16-
import mucsi96.traininglog.withings.oauth.WithingsClient;
1710

1811
@RestController
1912
@RequestMapping(value = "/weight", produces = MediaType.APPLICATION_JSON_VALUE)
2013
@RequiredArgsConstructor
2114
@RolesAllowed("user")
2215
public class WeightController {
2316

24-
private final WithingsService withingsService;
2517
private final WeightService weightService;
2618

27-
@PostMapping("/pull-from-withings")
28-
void sync(
29-
@Parameter(hidden = true) @RegisteredOAuth2AuthorizedClient(WithingsClient.id) OAuth2AuthorizedClient withingsAuthorizedClient) {
30-
31-
if (!weightService.getTodayWeight().isPresent()) {
32-
withingsService.getTodayWeight(withingsAuthorizedClient).ifPresent(weightService::saveWeight);
33-
}
34-
}
35-
3619
@GetMapping
37-
@ResponseBody
3820
WeightResponse weight() {
3921
Double weight = weightService.getTodayWeight().map(Weight::getValue).orElse(null);
4022
return WeightResponse
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package mucsi96.traininglog.withings;
2+
3+
import org.springframework.http.MediaType;
4+
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
5+
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
6+
import org.springframework.web.bind.annotation.PostMapping;
7+
import org.springframework.web.bind.annotation.RequestMapping;
8+
import org.springframework.web.bind.annotation.RestController;
9+
10+
import io.swagger.v3.oas.annotations.Parameter;
11+
import jakarta.annotation.security.RolesAllowed;
12+
import lombok.RequiredArgsConstructor;
13+
import mucsi96.traininglog.weight.WeightService;
14+
import mucsi96.traininglog.withings.oauth.WithingsClient;
15+
16+
@RestController
17+
@RequestMapping(value = "/withings", produces = MediaType.APPLICATION_JSON_VALUE)
18+
@RequiredArgsConstructor
19+
@RolesAllowed("user")
20+
public class WithingsController {
21+
22+
private final WithingsService withingsService;
23+
private final WeightService weightService;
24+
25+
@PostMapping("/sync")
26+
void sync(
27+
@Parameter(hidden = true) @RegisteredOAuth2AuthorizedClient(WithingsClient.id) OAuth2AuthorizedClient withingsAuthorizedClient) {
28+
29+
if (!weightService.getTodayWeight().isPresent()) {
30+
withingsService.getTodayWeight(withingsAuthorizedClient).ifPresent(weightService::saveWeight);
31+
}
32+
}
33+
}

server/src/main/resources/application.yml

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
server:
2-
forward-headers-strategy: framework
32
shutdown: graceful
43
management:
54
endpoint:

server/src/test/java/mucsi96/traininglog/BaseIntegrationTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import org.testcontainers.containers.PostgreSQLContainer;
1313

1414
@ActiveProfiles("test")
15-
@SpringBootTest
15+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
1616
@AutoConfigureMockMvc
1717
public class BaseIntegrationTest {
1818

Original file line numberDiff line numberDiff line change
@@ -1,105 +1,34 @@
11
package mucsi96.traininglog;
22

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

8-
import java.net.URI;
9-
import java.net.URLDecoder;
10-
import java.nio.charset.StandardCharsets;
116
import java.time.Instant;
12-
import java.time.LocalDate;
13-
import java.time.LocalDateTime;
14-
import java.time.LocalTime;
15-
import java.time.ZoneOffset;
16-
import java.util.List;
17-
import java.util.Optional;
187

198
import org.junit.jupiter.api.AfterEach;
209
import org.junit.jupiter.api.Test;
21-
import org.junit.jupiter.api.extension.RegisterExtension;
22-
import org.springframework.boot.test.context.SpringBootTest;
23-
import org.springframework.boot.test.web.server.LocalServerPort;
24-
import org.springframework.http.HttpHeaders;
25-
import org.springframework.http.MediaType;
2610
import org.springframework.mock.web.MockHttpServletResponse;
27-
import org.springframework.mock.web.MockHttpSession;
28-
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
29-
import org.springframework.test.context.DynamicPropertyRegistry;
30-
import org.springframework.test.context.DynamicPropertySource;
3111
import org.springframework.test.context.TestConstructor;
3212
import org.springframework.test.web.servlet.MockMvc;
33-
import org.springframework.web.util.UriComponents;
34-
import org.springframework.web.util.UriComponentsBuilder;
3513

36-
import com.github.tomakehurst.wiremock.client.WireMock;
37-
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
38-
import com.github.tomakehurst.wiremock.verification.LoggedRequest;
3914
import com.jayway.jsonpath.JsonPath;
4015

4116
import lombok.RequiredArgsConstructor;
42-
import mucsi96.traininglog.model.TestAuthorizedClient;
43-
import mucsi96.traininglog.repository.TestAuthorizedClientRepository;
4417
import mucsi96.traininglog.weight.Weight;
4518
import mucsi96.traininglog.weight.WeightRepository;
46-
import mucsi96.traininglog.withings.oauth.WithingsClient;
4719

48-
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
4920
@RequiredArgsConstructor
5021
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
5122
public class WeightControllerTests extends BaseIntegrationTest {
5223

5324
private final MockMvc mockMvc;
54-
private final TestAuthorizedClientRepository authorizedClientRepository;
5525
private final WeightRepository weightRepository;
5626

57-
@LocalServerPort
58-
private int port;
59-
60-
@RegisterExtension
61-
static WireMockExtension mockWithingsServer = WireMockExtension.newInstance()
62-
.options(wireMockConfig().dynamicPort())
63-
.build();
64-
65-
@DynamicPropertySource
66-
static void overrideProperties(DynamicPropertyRegistry registry) {
67-
68-
registry.add("spring.security.oauth2.client.provider.withings.authorization-uri",
69-
() -> mockWithingsServer.baseUrl() + "/oauth2_user/authorize2");
70-
registry.add(
71-
"spring.security.oauth2.client.provider.withings.token-uri",
72-
() -> mockWithingsServer.baseUrl() + "/v2/oauth2");
73-
74-
registry.add("spring.security.oauth2.client.registration.withings-client.client-id",
75-
() -> "test-withings-client-id");
76-
registry.add("spring.security.oauth2.client.registration.withings-client.client-secret",
77-
() -> "test-withings-client-secret");
78-
registry.add("withings.api.uri", () -> mockWithingsServer.baseUrl());
79-
}
80-
8127
@AfterEach
8228
void afterEach() {
83-
authorizedClientRepository.deleteAll();
8429
weightRepository.deleteAll();
8530
}
8631

87-
private void authorizeWithingsOAuth2Client() {
88-
TestAuthorizedClient authorizedClient = TestAuthorizedClient.builder()
89-
.clientRegistrationId("withings-client")
90-
.principalName("rob")
91-
.accessTokenType("Bearer")
92-
.accessTokenValue("test-access-token".getBytes(StandardCharsets.UTF_8))
93-
.accessTokenIssuedAt(LocalDateTime.now())
94-
.accessTokenExpiresAt(LocalDateTime.now().plusDays(1))
95-
.accessTokenScopes("user.metrics")
96-
.refreshTokenValue("test-refresh-token".getBytes(StandardCharsets.UTF_8))
97-
.refreshTokenIssuedAt(LocalDateTime.now())
98-
.build();
99-
100-
authorizedClientRepository.save(authorizedClient);
101-
}
102-
10332
@Test
10433
public void returns_not_authorized_if_no_preauth_headers_are_sent() throws Exception {
10534
MockHttpServletResponse response = mockMvc
@@ -137,110 +66,4 @@ public void returns_weight_from_database() throws Exception {
13766

13867
assertThat(JsonPath.parse(response.getContentAsString()).read("$.weight", Double.class)).isEqualTo(83.5);
13968
}
140-
141-
@Test
142-
public void returns_not_authorized_if_authorized_client_is_not_found() throws Exception {
143-
MockHttpServletResponse response = mockMvc
144-
.perform(
145-
post("/weight/pull-from-withings")
146-
.headers(getAuthHeaders("user")))
147-
.andReturn().getResponse();
148-
149-
assertThat(response.getStatus()).isEqualTo(401);
150-
assertThat(JsonPath.parse(response.getContentAsString()).read("$._links.oauth2Login.href", String.class))
151-
.isEqualTo("http://localhost/oauth2/authorization/withings-client");
152-
}
153-
154-
@Test
155-
public void redirects_to_withings_request_authorization_page() throws Exception {
156-
MockHttpServletResponse response = mockMvc
157-
.perform(
158-
get("/oauth2/authorization/withings-client"))
159-
.andReturn().getResponse();
160-
161-
assertThat(response.getStatus()).isEqualTo(302);
162-
URI redirectUrl = new URI(response.getRedirectedUrl());
163-
assertThat(redirectUrl).hasHost("localhost");
164-
assertThat(redirectUrl).hasPort(mockWithingsServer.getPort());
165-
assertThat(redirectUrl).hasPath("/oauth2_user/authorize2");
166-
assertThat(redirectUrl).hasParameter(OAuth2ParameterNames.RESPONSE_TYPE, "code");
167-
assertThat(redirectUrl).hasParameter(OAuth2ParameterNames.CLIENT_ID, "test-withings-client-id");
168-
assertThat(redirectUrl).hasParameter(OAuth2ParameterNames.SCOPE, "user.metrics");
169-
assertThat(redirectUrl).hasParameter(OAuth2ParameterNames.STATE);
170-
assertThat(redirectUrl).hasParameter(OAuth2ParameterNames.REDIRECT_URI,
171-
"http://localhost/authorize/oauth2/code/withings-client");
172-
}
173-
174-
@Test
175-
public void requests_withings_access_token_after_consent_is_granted() throws Exception {
176-
mockWithingsServer.stubFor(WireMock.post("/v2/oauth2").willReturn(
177-
WireMock.aResponse()
178-
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
179-
.withBodyFile("withings-authorize.json")));
180-
181-
MockHttpSession mockHttpSession = new MockHttpSession();
182-
MockHttpServletResponse response1 = mockMvc.perform(
183-
get("/oauth2/authorization/withings-client")
184-
.headers(getAuthHeaders("user"))
185-
.session(mockHttpSession))
186-
.andReturn().getResponse();
187-
UriComponents components = UriComponentsBuilder.fromUriString(response1.getRedirectedUrl()).build();
188-
String state = URLDecoder.decode(
189-
components.getQueryParams().getFirst(OAuth2ParameterNames.STATE),
190-
StandardCharsets.UTF_8);
191-
192-
MockHttpServletResponse response2 = mockMvc.perform(get("/authorize/oauth2/code/withings-client")
193-
.headers(getAuthHeaders("user"))
194-
.queryParam(OAuth2ParameterNames.STATE, state)
195-
.queryParam(OAuth2ParameterNames.CODE, "test-authorization-code")
196-
.session(mockHttpSession))
197-
.andReturn().getResponse();
198-
199-
assertThat(response2.getStatus()).isEqualTo(302);
200-
assertThat(response2.getRedirectedUrl()).isEqualTo("http://localhost/");
201-
202-
List<LoggedRequest> requests = mockWithingsServer
203-
.findAll(WireMock.postRequestedFor(WireMock.urlEqualTo("/v2/oauth2")));
204-
assertThat(requests).hasSize(1);
205-
URI uri = new URI("?" + requests.get(0).getBodyAsString());
206-
207-
Optional<TestAuthorizedClient> authorizedClient = authorizedClientRepository.findById(WithingsClient.id);
208-
209-
assertThat(authorizedClient.isPresent()).isTrue();
210-
assertThat(authorizedClient.get().getPrincipalName()).isEqualTo("rob");
211-
assertThat(new String(authorizedClient.get().getAccessTokenValue(), StandardCharsets.UTF_8))
212-
.isEqualTo("test-access-token");
213-
assertThat(new String(authorizedClient.get().getRefreshTokenValue(), StandardCharsets.UTF_8))
214-
.isEqualTo("test-refresh-token");
215-
assertThat(uri).hasParameter(OAuth2ParameterNames.GRANT_TYPE, "authorization_code");
216-
assertThat(uri).hasParameter(OAuth2ParameterNames.CODE, "test-authorization-code");
217-
assertThat(uri).hasParameter("action", "requesttoken");
218-
assertThat(uri).hasParameter(OAuth2ParameterNames.CLIENT_ID, "test-withings-client-id");
219-
assertThat(uri).hasParameter(OAuth2ParameterNames.CLIENT_SECRET, "test-withings-client-secret");
220-
}
221-
222-
@Test
223-
public void pulls_todays_weight_from_withings_to_database() throws Exception {
224-
authorizeWithingsOAuth2Client();
225-
long startTime = LocalDateTime.of(LocalDate.now(), LocalTime.MIN).toInstant(ZoneOffset.UTC).getEpochSecond();
226-
long endTime = LocalDateTime.of(LocalDate.now(), LocalTime.MAX).toInstant(ZoneOffset.UTC).getEpochSecond();
227-
mockWithingsServer.stubFor(WireMock
228-
.post(String.format("/measure?action=getmeas&meastype=1&category=1&startdate=%s&enddate=%s",
229-
startTime, endTime))
230-
.willReturn(
231-
WireMock.aResponse()
232-
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
233-
.withBodyFile("withings-measure.json")));
234-
MockHttpServletResponse response = mockMvc
235-
.perform(
236-
post("/weight/pull-from-withings")
237-
.headers(getAuthHeaders("user")))
238-
.andReturn().getResponse();
239-
240-
assertThat(response.getStatus()).isEqualTo(200);
241-
Optional<Weight> weight = weightRepository.findAll().stream().findFirst();
242-
assertThat(weight.isPresent()).isTrue();
243-
assertThat(weight.get().getValue()).isEqualTo(65.75);
244-
assertThat(weight.get().getCreatedAt().getEpochSecond()).isEqualTo(1594245600L);
245-
}
24669
}

0 commit comments

Comments
 (0)