Skip to content

Commit 4801595

Browse files
committed
Add test for sync
1 parent 7e8cda9 commit 4801595

File tree

11 files changed

+177
-39
lines changed

11 files changed

+177
-39
lines changed

TODO.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
- Add test for sync
2-
- Add test for weight get
1+
- Add test for access token refresh
2+
- Add test for sync forbidden
3+
- Split Weight controller into Weigth controller and withings controller
34
- Adjust the UI

server/src/main/java/mucsi96/traininglog/core/AppControllerAdvice.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
@ControllerAdvice
1414
public class AppControllerAdvice {
1515
@ExceptionHandler(ClientAuthorizationRequiredException.class)
16-
public ResponseEntity<RepresentationModel> handleClientAuthorizationRequired(
16+
public ResponseEntity<RepresentationModel<?>> handleClientAuthorizationRequired(
1717
ClientAuthorizationRequiredException ex) {
1818
String oauth2LoginUrl = ServletUriComponentsBuilder.fromCurrentServletMapping().path(
1919
OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/"

server/src/main/java/mucsi96/traininglog/configuration/SecurityConfiguration.java renamed to server/src/main/java/mucsi96/traininglog/core/SecurityConfiguration.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package mucsi96.traininglog.configuration;
1+
package mucsi96.traininglog.core;
22

33
import org.springframework.context.annotation.Bean;
44
import org.springframework.context.annotation.Configuration;
@@ -14,7 +14,6 @@
1414
import org.springframework.security.web.SecurityFilterChain;
1515

1616
import io.github.mucsi96.kubetools.security.KubetoolsSecurityConfigurer;
17-
import mucsi96.traininglog.core.RedirectToHomeRequestCache;
1817
import mucsi96.traininglog.oauth.AccessTokenResponseClient;
1918
import mucsi96.traininglog.oauth.AuthorizedClientManager;
2019
import mucsi96.traininglog.oauth.RefreshTokenResponseClient;

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

+5
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,18 @@
77
import jakarta.persistence.GeneratedValue;
88
import jakarta.persistence.GenerationType;
99
import jakarta.persistence.Id;
10+
import lombok.AccessLevel;
11+
import lombok.AllArgsConstructor;
1012
import lombok.Builder;
1113
import lombok.Data;
14+
import lombok.NoArgsConstructor;
1215
import lombok.NonNull;
1316

1417
@Data
1518
@Entity
1619
@Builder
20+
@AllArgsConstructor
21+
@NoArgsConstructor(access= AccessLevel.PRIVATE, force=true)
1722
public class Weight {
1823
@Id
1924
@GeneratedValue(strategy = GenerationType.IDENTITY)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package mucsi96.traininglog.withings;
2+
3+
import org.springframework.boot.context.properties.ConfigurationProperties;
4+
import org.springframework.context.annotation.Configuration;
5+
6+
import lombok.Data;
7+
8+
@Data
9+
@Configuration
10+
@ConfigurationProperties(prefix = "withings")
11+
public class WithingsConfiguration {
12+
private WithingsApiConfiguration api;
13+
14+
@Data
15+
public static class WithingsApiConfiguration {
16+
private String uri;
17+
}
18+
}

server/src/main/java/mucsi96/traininglog/withings/WithingsService.java

+12-16
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package mucsi96.traininglog.withings;
22

33
import java.time.Instant;
4-
import java.util.Calendar;
4+
import java.time.LocalDate;
5+
import java.time.LocalDateTime;
6+
import java.time.LocalTime;
7+
import java.time.ZoneOffset;
58
import java.util.List;
69
import java.util.Optional;
710

@@ -13,6 +16,7 @@
1316
import org.springframework.web.client.RestTemplate;
1417
import org.springframework.web.util.UriComponentsBuilder;
1518

19+
import lombok.RequiredArgsConstructor;
1620
import mucsi96.traininglog.weight.Weight;
1721
import mucsi96.traininglog.withings.data.GetMeasureResponse;
1822
import mucsi96.traininglog.withings.data.GetMeasureResponseBody;
@@ -21,30 +25,22 @@
2125
import mucsi96.traininglog.withings.oauth.WithingsClient;
2226

2327
@Service
28+
@RequiredArgsConstructor
2429
public class WithingsService {
2530

26-
int getStartDate() {
27-
Calendar cal = Calendar.getInstance();
28-
cal.set(Calendar.HOUR_OF_DAY, 0);
29-
cal.set(Calendar.MINUTE, 0);
30-
cal.set(Calendar.SECOND, 0);
31-
return (int) (cal.getTimeInMillis() / 1000);
32-
}
33-
34-
int getEndDate() {
35-
Calendar cal = Calendar.getInstance();
36-
return (int) (cal.getTimeInMillis() / 1000);
37-
}
31+
private final WithingsConfiguration withingsConfiguration;
3832

3933
private String getMeasureUrl() {
34+
long startTime = LocalDateTime.of(LocalDate.now(), LocalTime.MIN).toInstant(ZoneOffset.UTC).getEpochSecond();
35+
long endTime = LocalDateTime.of(LocalDate.now(), LocalTime.MAX).toInstant(ZoneOffset.UTC).getEpochSecond();
4036
return UriComponentsBuilder
41-
.fromHttpUrl("https://wbsapi.withings.net")
37+
.fromHttpUrl(withingsConfiguration.getApi().getUri())
4238
.path("/measure")
4339
.queryParam("action", "getmeas")
4440
.queryParam("meastype", 1)
4541
.queryParam("category", 1)
46-
.queryParam("startdate", getStartDate())
47-
.queryParam("enddate", getEndDate())
42+
.queryParam("startdate", startTime)
43+
.queryParam("enddate", endTime)
4844
.build()
4945
.encode()
5046
.toUriString();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{"properties": [{
2+
"name": "withings",
3+
"type": "mucsi96.traininglog.withings.WithingsConfiguration",
4+
"description": "Configuration for Withings API"
5+
}]}

server/src/main/resources/application.yml

+3
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,6 @@ logging:
5353
springdoc:
5454
swagger-ui:
5555
path: /
56+
withings:
57+
api:
58+
uri: https://wbsapi.withings.net

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

+91-11
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
import java.net.URI;
99
import java.net.URLDecoder;
1010
import java.nio.charset.StandardCharsets;
11+
import java.time.Instant;
12+
import java.time.LocalDate;
13+
import java.time.LocalDateTime;
14+
import java.time.LocalTime;
15+
import java.time.ZoneOffset;
1116
import java.util.List;
1217
import java.util.Optional;
1318

@@ -36,44 +41,63 @@
3641
import lombok.RequiredArgsConstructor;
3742
import mucsi96.traininglog.model.TestAuthorizedClient;
3843
import mucsi96.traininglog.repository.TestAuthorizedClientRepository;
44+
import mucsi96.traininglog.weight.Weight;
45+
import mucsi96.traininglog.weight.WeightRepository;
3946
import mucsi96.traininglog.withings.oauth.WithingsClient;
4047

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

46-
// OAuth2AuthorizationCodeAuthenticationProvider
47-
4853
private final MockMvc mockMvc;
4954
private final TestAuthorizedClientRepository authorizedClientRepository;
55+
private final WeightRepository weightRepository;
5056

5157
@LocalServerPort
5258
private int port;
5359

5460
@RegisterExtension
55-
static WireMockExtension withingsServer = WireMockExtension.newInstance()
61+
static WireMockExtension mockWithingsServer = WireMockExtension.newInstance()
5662
.options(wireMockConfig().dynamicPort())
5763
.build();
5864

5965
@DynamicPropertySource
6066
static void overrideProperties(DynamicPropertyRegistry registry) {
6167

6268
registry.add("spring.security.oauth2.client.provider.withings.authorization-uri",
63-
() -> withingsServer.baseUrl() + "/oauth2_user/authorize2");
69+
() -> mockWithingsServer.baseUrl() + "/oauth2_user/authorize2");
6470
registry.add(
6571
"spring.security.oauth2.client.provider.withings.token-uri",
66-
() -> withingsServer.baseUrl() + "/v2/oauth2");
72+
() -> mockWithingsServer.baseUrl() + "/v2/oauth2");
6773

6874
registry.add("spring.security.oauth2.client.registration.withings-client.client-id",
6975
() -> "test-withings-client-id");
7076
registry.add("spring.security.oauth2.client.registration.withings-client.client-secret",
7177
() -> "test-withings-client-secret");
78+
registry.add("withings.api.uri", () -> mockWithingsServer.baseUrl());
7279
}
7380

7481
@AfterEach
7582
void afterEach() {
7683
authorizedClientRepository.deleteAll();
84+
weightRepository.deleteAll();
85+
}
86+
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);
77101
}
78102

79103
@Test
@@ -86,12 +110,40 @@ public void returns_not_authorized_if_no_preauth_headers_are_sent() throws Excep
86110
assertThat(response.getStatus()).isEqualTo(401);
87111
}
88112

113+
@Test
114+
public void returns_forbidden_if_user_has_no_user_role() throws Exception {
115+
MockHttpServletResponse response = mockMvc
116+
.perform(
117+
get("/weight")
118+
.headers(getAuthHeaders("guest")))
119+
.andReturn().getResponse();
120+
121+
assertThat(response.getStatus()).isEqualTo(403);
122+
}
123+
124+
@Test
125+
public void returns_weight_from_database() throws Exception {
126+
Weight weight = Weight.builder()
127+
.value(83.5)
128+
.createdAt(Instant.now())
129+
.build();
130+
weightRepository.save(weight);
131+
132+
MockHttpServletResponse response = mockMvc
133+
.perform(
134+
get("/weight")
135+
.headers(getAuthHeaders("user")))
136+
.andReturn().getResponse();
137+
138+
assertThat(JsonPath.parse(response.getContentAsString()).read("$.weight", Double.class)).isEqualTo(83.5);
139+
}
140+
89141
@Test
90142
public void returns_not_authorized_if_authorized_client_is_not_found() throws Exception {
91143
MockHttpServletResponse response = mockMvc
92144
.perform(
93145
post("/weight/pull-from-withings")
94-
.headers(getAuthHeaders("guest")))
146+
.headers(getAuthHeaders("user")))
95147
.andReturn().getResponse();
96148

97149
assertThat(response.getStatus()).isEqualTo(401);
@@ -109,7 +161,7 @@ public void redirects_to_withings_request_authorization_page() throws Exception
109161
assertThat(response.getStatus()).isEqualTo(302);
110162
URI redirectUrl = new URI(response.getRedirectedUrl());
111163
assertThat(redirectUrl).hasHost("localhost");
112-
assertThat(redirectUrl).hasPort(withingsServer.getPort());
164+
assertThat(redirectUrl).hasPort(mockWithingsServer.getPort());
113165
assertThat(redirectUrl).hasPath("/oauth2_user/authorize2");
114166
assertThat(redirectUrl).hasParameter(OAuth2ParameterNames.RESPONSE_TYPE, "code");
115167
assertThat(redirectUrl).hasParameter(OAuth2ParameterNames.CLIENT_ID, "test-withings-client-id");
@@ -121,7 +173,7 @@ public void redirects_to_withings_request_authorization_page() throws Exception
121173

122174
@Test
123175
public void requests_withings_access_token_after_consent_is_granted() throws Exception {
124-
withingsServer.stubFor(WireMock.post("/v2/oauth2").willReturn(
176+
mockWithingsServer.stubFor(WireMock.post("/v2/oauth2").willReturn(
125177
WireMock.aResponse()
126178
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
127179
.withBodyFile("withings-authorize.json")));
@@ -147,20 +199,48 @@ public void requests_withings_access_token_after_consent_is_granted() throws Exc
147199
assertThat(response2.getStatus()).isEqualTo(302);
148200
assertThat(response2.getRedirectedUrl()).isEqualTo("http://localhost/");
149201

150-
List<LoggedRequest> requests = withingsServer.findAll(WireMock.postRequestedFor(WireMock.urlEqualTo("/v2/oauth2")));
202+
List<LoggedRequest> requests = mockWithingsServer
203+
.findAll(WireMock.postRequestedFor(WireMock.urlEqualTo("/v2/oauth2")));
151204
assertThat(requests).hasSize(1);
152205
URI uri = new URI("?" + requests.get(0).getBodyAsString());
153206

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

156209
assertThat(authorizedClient.isPresent()).isTrue();
157210
assertThat(authorizedClient.get().getPrincipalName()).isEqualTo("rob");
158-
assertThat(new String(authorizedClient.get().getAccessTokenValue(), "UTF-8")).isEqualTo("test-access-token");
159-
assertThat(new String(authorizedClient.get().getRefreshTokenValue(), "UTF-8")).isEqualTo("test-refresh-token");
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");
160215
assertThat(uri).hasParameter(OAuth2ParameterNames.GRANT_TYPE, "authorization_code");
161216
assertThat(uri).hasParameter(OAuth2ParameterNames.CODE, "test-authorization-code");
162217
assertThat(uri).hasParameter("action", "requesttoken");
163218
assertThat(uri).hasParameter(OAuth2ParameterNames.CLIENT_ID, "test-withings-client-id");
164219
assertThat(uri).hasParameter(OAuth2ParameterNames.CLIENT_SECRET, "test-withings-client-secret");
165220
}
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+
}
166246
}
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
package mucsi96.traininglog.model;
22

3-
import org.hibernate.annotations.JdbcType;
4-
import org.hibernate.annotations.Type;
5-
import org.hibernate.type.descriptor.jdbc.VarbinaryJdbcType;
3+
import java.time.LocalDateTime;
64

75
import jakarta.persistence.Entity;
86
import jakarta.persistence.Id;
9-
import jakarta.persistence.Lob;
107
import jakarta.persistence.Table;
118
import lombok.AccessLevel;
129
import lombok.AllArgsConstructor;
10+
import lombok.Builder;
1311
import lombok.Data;
1412
import lombok.NoArgsConstructor;
1513

1614
@Data
15+
@Builder()
1716
@Entity
1817
@Table(name = "oauth2_authorized_client")
1918
@AllArgsConstructor
@@ -24,9 +23,9 @@ public class TestAuthorizedClient {
2423
private String principalName;
2524
private String accessTokenType;
2625
private byte[] accessTokenValue;
27-
private String accessTokenIssuedAt;
28-
private String accessTokenExpiresAt;
26+
private LocalDateTime accessTokenIssuedAt;
27+
private LocalDateTime accessTokenExpiresAt;
2928
private String accessTokenScopes;
3029
private byte[] refreshTokenValue;
31-
private String refreshTokenIssuedAt;
30+
private LocalDateTime refreshTokenIssuedAt;
3231
}

0 commit comments

Comments
 (0)