22
33import io .github .sergejsvisockis .documentservice .repository .ApiKey ;
44import io .github .sergejsvisockis .documentservice .repository .ApiKeyRepository ;
5+ import io .github .sergejsvisockis .documentservice .utils .ApiKeyUtil ;
56import jakarta .servlet .http .HttpServletRequest ;
7+ import org .junit .jupiter .api .BeforeEach ;
68import org .junit .jupiter .api .Test ;
79import org .junit .jupiter .api .extension .ExtendWith ;
810import org .mockito .InjectMocks ;
911import org .mockito .Mock ;
10- import org .mockito .Mockito ;
12+ import org .mockito .MockedStatic ;
1113import org .mockito .junit .jupiter .MockitoExtension ;
1214import org .springframework .security .authentication .BadCredentialsException ;
1315import org .springframework .security .core .Authentication ;
1416
17+ import java .lang .reflect .InvocationTargetException ;
18+ import java .lang .reflect .Method ;
19+
1520import java .time .LocalDateTime ;
1621import java .util .Base64 ;
1722
1823import static io .github .sergejsvisockis .documentservice .auth .AuthenticationService .API_KEY_EXPIRED_MESSAGE ;
24+ import static io .github .sergejsvisockis .documentservice .auth .AuthenticationService .AUTH_TOKEN_HEADER_NAME ;
1925import static io .github .sergejsvisockis .documentservice .auth .AuthenticationService .INCORRECT_API_KEY_MESSAGE ;
2026import static io .github .sergejsvisockis .documentservice .auth .AuthenticationService .NO_API_KEY_FOUND_MESSAGE ;
2127import static org .junit .jupiter .api .Assertions .assertEquals ;
28+ import static org .junit .jupiter .api .Assertions .assertNotNull ;
2229import static org .junit .jupiter .api .Assertions .assertThrows ;
30+ import static org .junit .jupiter .api .Assertions .assertTrue ;
31+ import static org .junit .jupiter .api .Assertions .fail ;
32+ import static org .mockito .Mockito .mockStatic ;
33+ import static org .mockito .Mockito .times ;
34+ import static org .mockito .Mockito .verify ;
2335import static org .mockito .Mockito .when ;
2436
2537@ ExtendWith (MockitoExtension .class )
2638class AuthenticationServiceTest {
2739
28- private static final String AUTH_TOKEN_HEADER_NAME = "x-api-key" ;
29- private static final String VALID_API_KEY = "valid-api-key" ;
30- private static final String ENCODED_API_KEY = Base64 .getEncoder ().encodeToString (VALID_API_KEY .getBytes ());
40+ private static final String KEY_ID = "test-key-id" ;
41+ private static final String API_KEY = "test-api-key" ;
42+ private static final String VALID_HEADER_API_KEY = KEY_ID + "." + API_KEY ;
43+ private static final String HASHED_API_KEY = "hashedApiKey123" ;
44+ private static final String ENCODED_SALT = Base64 .getEncoder ().encodeToString ("test-salt" .getBytes ());
45+ private static final byte [] DECODED_SALT = "test-salt" .getBytes ();
3146
3247 @ Mock
3348 private ApiKeyRepository apiKeyRepository ;
@@ -38,58 +53,104 @@ class AuthenticationServiceTest {
3853 @ InjectMocks
3954 private AuthenticationService authenticationService ;
4055
56+ private ApiKey validApiKey ;
57+
58+ @ BeforeEach
59+ void setUp () {
60+ validApiKey = new ApiKey ();
61+ validApiKey .setApiKeyId (KEY_ID );
62+ validApiKey .setApiKey (HASHED_API_KEY );
63+ validApiKey .setSalt (ENCODED_SALT );
64+ validApiKey .setExpirationDate (LocalDateTime .now ().plusDays (1 ).toString ());
65+ }
66+
4167 @ Test
4268 void shouldReturnAuthenticationWhenApiKeyIsValid () {
4369 // given
44- ApiKey apiKey = new ApiKey ();
45- apiKey .setApiKey (ENCODED_API_KEY );
46- apiKey .setExpirationDate (LocalDateTime .now ().plusDays (1 ).toString ());
47-
48- when (request .getHeader (AUTH_TOKEN_HEADER_NAME )).thenReturn (VALID_API_KEY );
49- when (apiKeyRepository .findApiKey (ENCODED_API_KEY )).thenReturn (apiKey );
50-
51- // when
52- Authentication authentication = authenticationService .getAuthentication (request );
53-
54- // then
55- assertEquals (VALID_API_KEY , authentication .getPrincipal ());
70+ when (request .getHeader (AUTH_TOKEN_HEADER_NAME )).thenReturn (VALID_HEADER_API_KEY );
71+ when (apiKeyRepository .findApiKey (KEY_ID )).thenReturn (validApiKey );
72+
73+ try (MockedStatic <ApiKeyUtil > apiKeyUtilMock = mockStatic (ApiKeyUtil .class )) {
74+ apiKeyUtilMock .when (() -> ApiKeyUtil .decodeSalt (ENCODED_SALT )).thenReturn (DECODED_SALT );
75+ apiKeyUtilMock .when (() -> ApiKeyUtil .hashApiKey (API_KEY , DECODED_SALT )).thenReturn (HASHED_API_KEY );
76+
77+ // when
78+ Authentication authentication = authenticationService .getAuthentication (request );
79+
80+ // then
81+ assertNotNull (authentication );
82+ assertEquals (VALID_HEADER_API_KEY , authentication .getPrincipal ());
83+ apiKeyUtilMock .verify (() -> ApiKeyUtil .decodeSalt (ENCODED_SALT ));
84+ apiKeyUtilMock .verify (() -> ApiKeyUtil .hashApiKey (API_KEY , DECODED_SALT ));
85+ }
5686 }
5787
5888 @ Test
59- void shouldThrowExceptionWhenApiKeyIsNotFound () {
89+ void shouldThrowExceptionWhenApiKeyHeaderIsMissing () {
6090 // given
61- when (request .getHeader (AUTH_TOKEN_HEADER_NAME )).thenReturn (VALID_API_KEY );
62- when (apiKeyRepository .findApiKey (ENCODED_API_KEY )).thenReturn (null );
91+ when (request .getHeader (AUTH_TOKEN_HEADER_NAME )).thenReturn (null );
6392
6493 // when & then
6594 BadCredentialsException exception = assertThrows (BadCredentialsException .class ,
6695 () -> authenticationService .getAuthentication (request ));
67- assertEquals (NO_API_KEY_FOUND_MESSAGE , exception .getMessage ());
96+ assertEquals (INCORRECT_API_KEY_MESSAGE , exception .getMessage ());
6897 }
6998
7099 @ Test
71- void shouldThrowExceptionWhenApiKeyIsNull () {
100+ void shouldThrowExceptionWhenApiKeyFormatIsInvalid () {
72101 // given
73- when (request .getHeader (AUTH_TOKEN_HEADER_NAME )).thenReturn (null );
102+ when (request .getHeader (AUTH_TOKEN_HEADER_NAME )).thenReturn ("invalid-format" );
74103
75104 // when & then
76- BadCredentialsException exception = assertThrows (BadCredentialsException .class ,
105+ assertThrows (ArrayIndexOutOfBoundsException .class ,
77106 () -> authenticationService .getAuthentication (request ));
78- assertEquals (INCORRECT_API_KEY_MESSAGE , exception .getMessage ());
79107 }
80108
81109 @ Test
82- void shouldThrowExceptionWhenApiKeyHasExpired () {
110+ void shouldThrowExceptionWhenApiKeyIsNotFound () {
83111 // given
84- ApiKey apiKey = Mockito .mock (ApiKey .class );
85- when (apiKey .getExpirationDate ()).thenReturn (LocalDateTime .now ().minusDays (1 ).toString ());
86- when (request .getHeader (AUTH_TOKEN_HEADER_NAME )).thenReturn (VALID_API_KEY );
87- when (apiKeyRepository .findApiKey (ENCODED_API_KEY )).thenReturn (apiKey );
88-
112+ when (request .getHeader (AUTH_TOKEN_HEADER_NAME )).thenReturn (VALID_HEADER_API_KEY );
113+ when (apiKeyRepository .findApiKey (KEY_ID )).thenReturn (null );
89114
90115 // when & then
91116 BadCredentialsException exception = assertThrows (BadCredentialsException .class ,
92117 () -> authenticationService .getAuthentication (request ));
93- assertEquals (API_KEY_EXPIRED_MESSAGE , exception .getMessage ());
118+ assertEquals (NO_API_KEY_FOUND_MESSAGE , exception .getMessage ());
119+ }
120+
121+ @ Test
122+ void shouldThrowExceptionWhenApiKeyHashDoesNotMatch () {
123+ // given
124+ when (request .getHeader (AUTH_TOKEN_HEADER_NAME )).thenReturn (VALID_HEADER_API_KEY );
125+ when (apiKeyRepository .findApiKey (KEY_ID )).thenReturn (validApiKey );
126+
127+ try (MockedStatic <ApiKeyUtil > apiKeyUtilMock = mockStatic (ApiKeyUtil .class )) {
128+ apiKeyUtilMock .when (() -> ApiKeyUtil .decodeSalt (ENCODED_SALT )).thenReturn (DECODED_SALT );
129+ apiKeyUtilMock .when (() -> ApiKeyUtil .hashApiKey (API_KEY , DECODED_SALT )).thenReturn ("different-hash" );
130+
131+ // when & then
132+ BadCredentialsException exception = assertThrows (BadCredentialsException .class ,
133+ () -> authenticationService .getAuthentication (request ));
134+ assertEquals (INCORRECT_API_KEY_MESSAGE , exception .getMessage ());
135+ }
136+ }
137+
138+ @ Test
139+ void shouldUseCacheForRepeatedRequests () {
140+ // given
141+ when (request .getHeader (AUTH_TOKEN_HEADER_NAME )).thenReturn (VALID_HEADER_API_KEY );
142+ when (apiKeyRepository .findApiKey (KEY_ID )).thenReturn (validApiKey );
143+
144+ try (MockedStatic <ApiKeyUtil > apiKeyUtilMock = mockStatic (ApiKeyUtil .class )) {
145+ apiKeyUtilMock .when (() -> ApiKeyUtil .decodeSalt (ENCODED_SALT )).thenReturn (DECODED_SALT );
146+ apiKeyUtilMock .when (() -> ApiKeyUtil .hashApiKey (API_KEY , DECODED_SALT )).thenReturn (HASHED_API_KEY );
147+
148+ // when
149+ authenticationService .getAuthentication (request );
150+ authenticationService .getAuthentication (request );
151+
152+ // then
153+ verify (apiKeyRepository , times (1 )).findApiKey (KEY_ID );
154+ }
94155 }
95156}
0 commit comments