add rotate tests
This commit is contained in:
@@ -4,9 +4,12 @@ import com.ddf.vodsystem.dto.properties.AuthProperties;
|
||||
import com.ddf.vodsystem.entities.RefreshToken;
|
||||
import com.ddf.vodsystem.entities.TokenFamily;
|
||||
import com.ddf.vodsystem.entities.User;
|
||||
import com.ddf.vodsystem.exceptions.NotAuthenticated;
|
||||
import com.ddf.vodsystem.repositories.RefreshTokenRepository;
|
||||
import com.ddf.vodsystem.repositories.TokenFamilyRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -16,9 +19,11 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.time.*;
|
||||
import java.util.HexFormat;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class MutableClock extends Clock {
|
||||
private Instant now;
|
||||
@@ -46,6 +51,7 @@ class TokenServiceTest {
|
||||
@Captor ArgumentCaptor<RefreshToken> tokenCaptor;
|
||||
|
||||
private static final HexFormat HEX = HexFormat.of();
|
||||
private static final String rawToken = "deadbeefdeadbeefdeadbeefdeadbeef";
|
||||
|
||||
AuthProperties authProperties;
|
||||
TokenService tokenService;
|
||||
@@ -61,6 +67,7 @@ class TokenServiceTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
|
||||
authProperties = new AuthProperties(
|
||||
"a-secret",
|
||||
new AuthProperties.Expiration(
|
||||
@@ -81,62 +88,173 @@ class TokenServiceTest {
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSession_createsTokenFamily() {
|
||||
User user = createUser(1L, "user", "user@gmail.com");
|
||||
@Nested
|
||||
@DisplayName("createSession()")
|
||||
class createSession {
|
||||
@Test
|
||||
void createSession_createsTokenFamily() {
|
||||
User user = createUser(1L, "user", "user@gmail.com");
|
||||
|
||||
tokenService.createSession(user);
|
||||
tokenService.createSession(user);
|
||||
|
||||
verify(tokenFamilyRepository).save(familyCaptor.capture());
|
||||
verify(refreshTokenRepository).save(tokenCaptor.capture());
|
||||
verify(tokenFamilyRepository).save(familyCaptor.capture());
|
||||
verify(refreshTokenRepository).save(tokenCaptor.capture());
|
||||
|
||||
TokenFamily tokenFamily = familyCaptor.getValue();
|
||||
RefreshToken refreshToken = tokenCaptor.getValue();
|
||||
TokenFamily tokenFamily = familyCaptor.getValue();
|
||||
RefreshToken refreshToken = tokenCaptor.getValue();
|
||||
|
||||
assertSame(tokenFamily.getUser(), user);
|
||||
assertSame(refreshToken.getTokenFamily(), tokenFamily);
|
||||
assertEquals(
|
||||
tokenFamily.getExpiresAt(),
|
||||
clock.instant().plusMillis(authProperties.expiration().tokenFamily().toMillis())
|
||||
);
|
||||
assertSame(tokenFamily.getUser(), user);
|
||||
assertSame(refreshToken.getTokenFamily(), tokenFamily);
|
||||
assertEquals(
|
||||
tokenFamily.getExpiresAt(),
|
||||
clock.instant().plusMillis(authProperties.expiration().tokenFamily().toMillis())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSession_savesRefreshTokenCreatedAtAndExpires() {
|
||||
User user = createUser(1L, "user", "user");
|
||||
|
||||
tokenService.createSession(user);
|
||||
|
||||
verify(refreshTokenRepository).save(tokenCaptor.capture());
|
||||
RefreshToken refreshToken = tokenCaptor.getValue();
|
||||
|
||||
assertEquals(refreshToken.getCreatedAt(), clock.instant());
|
||||
assertEquals(
|
||||
refreshToken.getExpiresAt(),
|
||||
clock.instant().plusMillis(authProperties.expiration().refreshToken().toMillis())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSession_returnsHexTokenOf64Characters() {
|
||||
User user = createUser(1L, "user", "user");
|
||||
|
||||
String token = tokenService.createSession(user);
|
||||
|
||||
assertEquals(64, token.length());
|
||||
assertTrue(token.matches("[0-9a-fA-F]+"), () -> "not hex: " + token);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSession_doesNotStoreRawToken() {
|
||||
User user = createUser(1L, "user", "user");
|
||||
|
||||
String token = tokenService.createSession(user);
|
||||
|
||||
verify(refreshTokenRepository).save(tokenCaptor.capture());
|
||||
RefreshToken refreshToken = tokenCaptor.getValue();
|
||||
|
||||
assertNotEquals(refreshToken.getTokenHash(), HEX.parseHex(token));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSession_savesRefreshTokenCreatedAtAndExpires() {
|
||||
User user = createUser(1L, "user", "user");
|
||||
// Rotate
|
||||
@Nested
|
||||
@DisplayName("rotate()")
|
||||
class rotate {
|
||||
// Input val
|
||||
@Test
|
||||
void nullToken_throwsNotAuthenticated() {
|
||||
assertThrows(
|
||||
NotAuthenticated.class,
|
||||
() -> tokenService.rotate(null)
|
||||
);
|
||||
}
|
||||
|
||||
tokenService.createSession(user);
|
||||
@Test
|
||||
void blankToken_throwsNotAuthenticated() {
|
||||
assertThrows(
|
||||
NotAuthenticated.class,
|
||||
() -> tokenService.rotate("")
|
||||
);
|
||||
}
|
||||
|
||||
verify(refreshTokenRepository).save(tokenCaptor.capture());
|
||||
RefreshToken refreshToken = tokenCaptor.getValue();
|
||||
// Invalid cases
|
||||
@Test
|
||||
void unknownToken_throwsNotAuthenticated() {
|
||||
when(refreshTokenRepository.findByHash(any())).thenReturn(
|
||||
Optional.empty()
|
||||
);
|
||||
|
||||
assertEquals(refreshToken.getCreatedAt(), clock.instant());
|
||||
assertEquals(
|
||||
refreshToken.getExpiresAt(),
|
||||
clock.instant().plusMillis(authProperties.expiration().refreshToken().toMillis())
|
||||
);
|
||||
}
|
||||
assertThrows(
|
||||
NotAuthenticated.class,
|
||||
() -> tokenService.rotate(rawToken)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSession_returnsHexTokenOf64Characters() {
|
||||
User user = createUser(1L, "user", "user");
|
||||
@Test
|
||||
void revokedFamily_throwsNotAuthenticated() {
|
||||
TokenFamily tokenFamily = new TokenFamily();
|
||||
tokenFamily.setId(1L);
|
||||
tokenFamily.setRevokedAt(Instant.now(clock));
|
||||
|
||||
String token = tokenService.createSession(user);
|
||||
RefreshToken refreshToken = new RefreshToken();
|
||||
refreshToken.setTokenFamily(tokenFamily);
|
||||
|
||||
assertEquals(64, token.length());
|
||||
assertTrue(token.matches("[0-9a-fA-F]+"), () -> "not hex: " + token);
|
||||
}
|
||||
clock.advance(Duration.ofMinutes(1));
|
||||
|
||||
@Test
|
||||
void createSession_doesNotStoreRawToken() {
|
||||
User user = createUser(1L, "user", "user");
|
||||
when(refreshTokenRepository.findByHash(any())).thenReturn(
|
||||
Optional.of(refreshToken)
|
||||
);
|
||||
|
||||
String token = tokenService.createSession(user);
|
||||
assertThrows(
|
||||
NotAuthenticated.class,
|
||||
() -> tokenService.rotate(rawToken)
|
||||
);
|
||||
}
|
||||
|
||||
verify(refreshTokenRepository).save(tokenCaptor.capture());
|
||||
RefreshToken refreshToken = tokenCaptor.getValue();
|
||||
// Happy Path
|
||||
@Test
|
||||
void unusedToken_returnsUserIdAndRefreshToken() {
|
||||
TokenFamily tokenFamily = new TokenFamily();
|
||||
tokenFamily.setId(1L);
|
||||
tokenFamily.setUser(createUser(1L, "user", "user"));
|
||||
tokenFamily.setExpiresAt(Instant.now(clock).plusSeconds(10));
|
||||
|
||||
RefreshToken refreshToken = new RefreshToken();
|
||||
refreshToken.setTokenFamily(tokenFamily);
|
||||
refreshToken.setExpiresAt(Instant.now(clock).plusSeconds(10));
|
||||
|
||||
when(refreshTokenRepository.findByHash(any()))
|
||||
.thenReturn(Optional.of(refreshToken));
|
||||
|
||||
TokenService.Rotation rotation = tokenService.rotate(rawToken);
|
||||
|
||||
assertEquals(1L, rotation.userId());
|
||||
assertNotEquals(rawToken, rotation.newRefreshToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
void unsedToken_savesRefreshToken() {
|
||||
TokenFamily tokenFamily = new TokenFamily();
|
||||
tokenFamily.setId(1L);
|
||||
tokenFamily.setUser(createUser(1L, "user", "user"));
|
||||
tokenFamily.setExpiresAt(Instant.now(clock).plusSeconds(10));
|
||||
|
||||
RefreshToken refreshToken = new RefreshToken();
|
||||
refreshToken.setTokenFamily(tokenFamily);
|
||||
refreshToken.setExpiresAt(Instant.now(clock).plusSeconds(10));
|
||||
|
||||
when(refreshTokenRepository.findByHash(any()))
|
||||
.thenReturn(Optional.of(refreshToken));
|
||||
|
||||
tokenService.rotate(rawToken);
|
||||
|
||||
verify(refreshTokenRepository, times(2)).save(tokenCaptor.capture());
|
||||
|
||||
RefreshToken newToken = tokenCaptor.getAllValues().stream()
|
||||
.filter(t -> t.getUsedAt() == null)
|
||||
.reduce((a, b) -> { throw new AssertionError("expected exactly one unused token saved"); })
|
||||
.orElseThrow(() -> new AssertionError("no unused token was saved"));
|
||||
|
||||
assertNotEquals(refreshToken.getTokenHash(), newToken.getTokenHash());
|
||||
assertEquals(refreshToken.getTokenFamily().getUser(), newToken.getTokenFamily().getUser()); // same owner
|
||||
assertEquals(refreshToken.getTokenFamily(), newToken.getTokenFamily()); // same rotation family, if you model one
|
||||
assertNull(newToken.getUsedAt());
|
||||
}
|
||||
|
||||
assertNotEquals(refreshToken.getTokenHash(), HEX.parseHex(token));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user