Skip to content

Commit 9965925

Browse files
author
rama
committed
[auth] Init Unit test using junit & jacoco for report coverage github#2
- Refactor TokenResponse attribute to camelCase
1 parent b72d7e1 commit 9965925

16 files changed

Lines changed: 688 additions & 0 deletions
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.example.auth.application.account;
2+
3+
import com.example.auth.domain.account.Account;
4+
import com.example.auth.domain.account.AccountRepository;
5+
import com.example.auth.domain.account.PasswordHasher;
6+
import com.example.auth.web.error.EmailAlreadyExistsException;
7+
import com.example.auth.web.error.UsernameAlreadyExistsException;
8+
import org.junit.jupiter.api.BeforeEach;
9+
import org.junit.jupiter.api.Test;
10+
import org.mockito.ArgumentCaptor;
11+
12+
import java.util.Optional;
13+
import java.util.UUID;
14+
15+
import static org.assertj.core.api.Assertions.*;
16+
import static org.mockito.Mockito.*;
17+
18+
class AccountCommandServiceTest {
19+
20+
private AccountRepository repo;
21+
private PasswordHasher hasher;
22+
private AccountCommands service;
23+
24+
@BeforeEach
25+
void setUp() {
26+
repo = mock(AccountRepository.class);
27+
hasher = mock(PasswordHasher.class);
28+
service = new AccountCommandService(repo, hasher);
29+
}
30+
31+
@Test
32+
void register_success_savesEncodedPassword_andReturnsId() {
33+
when(repo.findByUsername("alice")).thenReturn(Optional.empty());
34+
when(repo.findByEmail("a@x.io")).thenReturn(Optional.empty());
35+
when(hasher.encode("secret")).thenReturn("HASH");
36+
37+
var saved = Account.of(UUID.randomUUID(),"alice","a@x.io","HASH","ACTIVE",null);
38+
when(repo.save(any())).thenReturn(saved);
39+
40+
var id = service.register("alice", "a@x.io", "secret");
41+
42+
assertThat(id).isEqualTo(saved.getId());
43+
44+
ArgumentCaptor<Account> cap = ArgumentCaptor.forClass(Account.class);
45+
verify(repo).save(cap.capture());
46+
assertThat(cap.getValue().getPasswordHash()).isEqualTo("HASH");
47+
}
48+
49+
@Test
50+
void register_usernameTaken_throwsApiException() {
51+
when(repo.findByUsername("alice")).thenReturn(Optional.of(mock(Account.class)));
52+
assertThatThrownBy(() -> service.register("alice","a@x.io","secret"))
53+
.isInstanceOf(UsernameAlreadyExistsException.class);
54+
verify(repo, never()).save(any());
55+
}
56+
57+
@Test
58+
void register_emailTaken_throwsApiException() {
59+
when(repo.findByUsername("alice")).thenReturn(Optional.empty());
60+
when(repo.findByEmail("a@x.io")).thenReturn(Optional.of(mock(Account.class)));
61+
assertThatThrownBy(() -> service.register("alice","a@x.io","secret"))
62+
.isInstanceOf(EmailAlreadyExistsException.class);
63+
}
64+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package com.example.auth.application.auth;
2+
3+
import com.example.auth.application.auth.AuthCommands.TokenPair;
4+
import com.example.auth.domain.account.Account;
5+
import com.example.auth.domain.account.AccountRepository;
6+
import com.example.auth.domain.account.PasswordHasher;
7+
import com.example.auth.domain.entitlement.EntitlementClient;
8+
import com.example.auth.domain.entitlement.Entitlements;
9+
import com.example.auth.domain.token.RefreshToken;
10+
import com.example.auth.domain.token.RefreshTokenRepository;
11+
import com.example.auth.domain.token.jwt.JwtProvider;
12+
import com.example.auth.web.error.InvalidCredentialsException;
13+
import org.junit.jupiter.api.BeforeEach;
14+
import org.junit.jupiter.api.Test;
15+
16+
import java.time.Instant;
17+
import java.time.OffsetDateTime;
18+
import java.time.ZoneOffset;
19+
import java.util.List;
20+
import java.util.Optional;
21+
import java.util.UUID;
22+
23+
import static org.assertj.core.api.Assertions.*;
24+
import static org.mockito.Mockito.*;
25+
26+
class AuthCommandServiceLoginTest {
27+
28+
private AccountRepository accRepo;
29+
private PasswordHasher hasher;
30+
private EntitlementClient iam;
31+
private JwtProvider jwt;
32+
private RefreshTokenRepository rt;
33+
private AuthCommands svc;
34+
35+
@BeforeEach
36+
void setUp() {
37+
accRepo = mock(AccountRepository.class);
38+
hasher = mock(PasswordHasher.class);
39+
iam = mock(EntitlementClient.class);
40+
jwt = mock(JwtProvider.class);
41+
rt = mock(RefreshTokenRepository.class);
42+
svc = new AuthCommandService(accRepo, hasher, iam, jwt, rt);
43+
}
44+
45+
@Test
46+
void login_success() {
47+
var accId = UUID.randomUUID();
48+
var acc = Account.of(accId,"alice","a@x.io","HASH","ACTIVE", OffsetDateTime.now(ZoneOffset.UTC));
49+
when(accRepo.findByUsernameOrEmail("alice")).thenReturn(Optional.of(acc));
50+
when(hasher.matches("secret","HASH")).thenReturn(true);
51+
52+
when(iam.fetchEntitlements(accId))
53+
.thenReturn(Entitlements.of(accId, 4, List.of("ADMIN"), Instant.now()));
54+
when(jwt.generateAccessToken(eq(accId), anyList(), eq(4), any()))
55+
.thenReturn("jwt");
56+
when(jwt.getAccessTtlSeconds()).thenReturn(900L);
57+
58+
var rotated = RefreshToken.of(UUID.randomUUID(), accId,
59+
OffsetDateTime.now(ZoneOffset.UTC).plusDays(7), false);
60+
when(rt.create(any(), eq(accId), any())).thenReturn(rotated);
61+
62+
TokenPair out = svc.login("alice", "secret");
63+
64+
assertThat(out.tokenType()).isEqualTo("Bearer");
65+
assertThat(out.accessToken()).isEqualTo("jwt");
66+
assertThat(out.expiresIn()).isEqualTo(900);
67+
assertThat(out.refreshToken()).isEqualTo(rotated.getId().toString());
68+
}
69+
70+
@Test
71+
void login_wrongPassword_throws() {
72+
var acc = Account.of(UUID.randomUUID(),"alice","a@x.io","HASH","ACTIVE", OffsetDateTime.now(ZoneOffset.UTC));
73+
when(accRepo.findByUsernameOrEmail("alice")).thenReturn(Optional.of(acc));
74+
when(hasher.matches("bad","HASH")).thenReturn(false);
75+
76+
assertThatThrownBy(() -> svc.login("alice","bad"))
77+
.isInstanceOf(InvalidCredentialsException.class);
78+
79+
verifyNoInteractions(iam, jwt, rt);
80+
}
81+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.example.auth.application.auth;
2+
3+
import com.example.auth.application.auth.AuthCommands.TokenPair;
4+
import com.example.auth.domain.entitlement.EntitlementClient;
5+
import com.example.auth.domain.entitlement.Entitlements;
6+
import com.example.auth.domain.token.RefreshToken;
7+
import com.example.auth.domain.token.RefreshTokenRepository;
8+
import com.example.auth.domain.token.jwt.JwtProvider;
9+
import com.example.auth.web.error.RefreshTokenInvalidException;
10+
import org.junit.jupiter.api.BeforeEach;
11+
import org.junit.jupiter.api.Test;
12+
13+
import java.time.Instant;
14+
import java.time.OffsetDateTime;
15+
import java.time.ZoneOffset;
16+
import java.util.List;
17+
import java.util.Optional;
18+
import java.util.UUID;
19+
20+
import static org.assertj.core.api.Assertions.*;
21+
import static org.mockito.Mockito.*;
22+
23+
class AuthCommandServiceRefreshTest {
24+
25+
private RefreshTokenRepository rt;
26+
private EntitlementClient iam;
27+
private JwtProvider jwt;
28+
private AuthCommands svc;
29+
30+
@BeforeEach
31+
void setUp() {
32+
rt = mock(RefreshTokenRepository.class);
33+
iam = mock(EntitlementClient.class);
34+
jwt = mock(JwtProvider.class);
35+
svc = new AuthCommandService(null, null, iam, jwt, rt);
36+
}
37+
38+
@Test
39+
void refresh_success_rotatesToken() {
40+
var accId = UUID.randomUUID();
41+
var cur = RefreshToken.of(UUID.randomUUID(), accId, OffsetDateTime.now(ZoneOffset.UTC).plusDays(5), false);
42+
when(rt.findById(cur.getId())).thenReturn(Optional.of(cur));
43+
44+
var rotated = RefreshToken.of(UUID.randomUUID(), accId, OffsetDateTime.now(ZoneOffset.UTC).plusDays(7), false);
45+
when(rt.create(any(), eq(accId), any())).thenReturn(rotated);
46+
47+
when(iam.fetchEntitlements(accId))
48+
.thenReturn(Entitlements.of(accId, 4, List.of("ADMIN"), Instant.now()));
49+
when(jwt.generateAccessToken(eq(accId), anyList(), eq(4), any()))
50+
.thenReturn("newjwt");
51+
when(jwt.getAccessTtlSeconds()).thenReturn(900L);
52+
53+
TokenPair out = svc.refresh(cur.getId().toString());
54+
55+
verify(rt).consume(cur.getId());
56+
assertThat(out.refreshToken()).isEqualTo(rotated.getId().toString());
57+
assertThat(out.accessToken()).isEqualTo("newjwt");
58+
}
59+
60+
@Test
61+
void refresh_invalidToken_throws() {
62+
var id = UUID.randomUUID();
63+
when(rt.findById(id)).thenReturn(Optional.empty());
64+
assertThatThrownBy(() -> svc.refresh(id.toString()))
65+
.isInstanceOf(RefreshTokenInvalidException.class);
66+
}
67+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.example.auth.application.jwk;
2+
3+
import com.example.auth.domain.token.jwt.JwtProvider;
4+
import org.junit.jupiter.api.Test;
5+
6+
import java.util.Map;
7+
8+
import static org.assertj.core.api.Assertions.assertThat;
9+
import static org.mockito.Mockito.*;
10+
11+
class JwkQueryServiceTest {
12+
@Test
13+
void handle_returnsProviderJwks() {
14+
JwtProvider p = mock(JwtProvider.class);
15+
when(p.currentJwks()).thenReturn(Map.of("keys", "test"));
16+
JwkQueries q = new JwkQueryService(p);
17+
assertThat(q.jwks()).containsEntry("keys", "test");
18+
}
19+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.example.auth.domain.account;
2+
3+
import com.example.auth.domain.shared.ValidationRules;
4+
import org.junit.jupiter.api.Test;
5+
6+
import static org.assertj.core.api.Assertions.assertThat;
7+
8+
class ValidationRulesTest {
9+
10+
@Test
11+
void usernameRegex_acceptsLettersDigitsUnderscoreDot_3to32() {
12+
var r = ValidationRules.USERNAME_PATTERN;
13+
14+
assertThat(r.matcher("abc").matches()).isTrue();
15+
assertThat(r.matcher("a_b.c1").matches()).isTrue();
16+
assertThat(r.matcher("a".repeat(32)).matches()).isTrue();
17+
18+
assertThat(r.matcher("ab").matches()).isFalse();
19+
assertThat(r.matcher("a".repeat(33)).matches()).isFalse();
20+
assertThat(r.matcher("bad*char").matches()).isFalse();
21+
assertThat(r.matcher(" space ").matches()).isFalse();
22+
}
23+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.example.auth.domain.token;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import java.time.OffsetDateTime;
6+
import java.time.ZoneOffset;
7+
import java.util.UUID;
8+
9+
import static org.assertj.core.api.Assertions.assertThat;
10+
11+
class RefreshTokenValueObjectTest {
12+
@Test
13+
void of_setsFields() {
14+
var id = UUID.randomUUID();
15+
var acc = UUID.randomUUID();
16+
var exp = OffsetDateTime.now(ZoneOffset.UTC).plusDays(7);
17+
18+
var rt = RefreshToken.of(id, acc, exp, false);
19+
20+
assertThat(rt.getId()).isEqualTo(id);
21+
assertThat(rt.getAccountId()).isEqualTo(acc);
22+
assertThat(rt.isRevoked()).isFalse();
23+
}
24+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package com.example.auth.infrastructure.iam;
2+
3+
import com.example.auth.domain.entitlement.Entitlements;
4+
import com.example.auth.web.error.IamUnavailableException;
5+
import com.github.tomakehurst.wiremock.WireMockServer;
6+
import org.junit.jupiter.api.*;
7+
import org.springframework.web.reactive.function.client.WebClient;
8+
9+
import java.util.List;
10+
import java.util.UUID;
11+
12+
import static com.github.tomakehurst.wiremock.client.WireMock.*;
13+
import static org.assertj.core.api.Assertions.*;
14+
15+
class IamEntitlementClientImplTest {
16+
17+
WireMockServer wm;
18+
IamEntitlementClientImpl client;
19+
20+
@BeforeEach
21+
void setup() {
22+
wm = new WireMockServer(0);
23+
wm.start();
24+
configureFor("localhost", wm.port());
25+
26+
WebClient webClient = WebClient.builder()
27+
.baseUrl("http://localhost:" + wm.port())
28+
.build();
29+
30+
client = new IamEntitlementClientImpl(webClient, 1000, "SVC-TOKEN");
31+
}
32+
33+
@AfterEach
34+
void tearDown() { wm.stop(); }
35+
36+
@Test
37+
void fetchEntitlements_success_mergesScopesAndRoles() {
38+
UUID id = UUID.randomUUID();
39+
40+
stubFor(get(urlEqualTo("/internal/v1/entitlements/" + id))
41+
.withHeader("X-Internal-Token", equalTo("SVC-TOKEN"))
42+
.willReturn(okJson("{\"perm_ver\":4,\"scopes\":[\"product:brand:read\"]}")));
43+
44+
stubFor(get(urlEqualTo("/internal/v1/users/" + id + "/roles"))
45+
.withHeader("X-Internal-Token", equalTo("SVC-TOKEN"))
46+
.willReturn(okJson("[\"ADMIN\"]")));
47+
48+
Entitlements ent = client.fetchEntitlements(id);
49+
50+
assertThat(ent.getPermVer()).isEqualTo(4);
51+
assertThat(ent.getRoles()).isEqualTo(List.of("ADMIN"));
52+
}
53+
54+
@Test
55+
void fetchEntitlements_upstream4xx_throwsServiceUnavailableWithReason() {
56+
UUID id = UUID.randomUUID();
57+
58+
stubFor(get(urlMatching("/internal/v1/.*"))
59+
.willReturn(aResponse().withStatus(404)));
60+
61+
assertThatThrownBy(() -> client.fetchEntitlements(id))
62+
.isInstanceOf(IamUnavailableException.class)
63+
.hasMessageContaining("iam_unavailable");
64+
}
65+
}

0 commit comments

Comments
 (0)