|
21 | 21 | import java.time.Instant; |
22 | 22 | import java.time.ZoneOffset; |
23 | 23 | import java.time.temporal.ChronoUnit; |
| 24 | +import java.util.List; |
24 | 25 |
|
25 | 26 | import org.junit.jupiter.api.AfterEach; |
26 | 27 | import org.junit.jupiter.api.BeforeEach; |
27 | 28 | import org.junit.jupiter.api.Test; |
| 29 | +import org.mockito.ArgumentMatchers; |
28 | 30 |
|
29 | 31 | import org.springframework.jdbc.core.JdbcOperations; |
30 | 32 | import org.springframework.jdbc.core.JdbcTemplate; |
| 33 | +import org.springframework.jdbc.core.PreparedStatementSetter; |
| 34 | +import org.springframework.jdbc.core.RowMapper; |
31 | 35 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; |
32 | 36 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; |
33 | 37 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; |
34 | 38 |
|
35 | 39 | import static org.assertj.core.api.Assertions.assertThat; |
36 | 40 | import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
| 41 | +import static org.mockito.ArgumentMatchers.any; |
37 | 42 | import static org.mockito.BDDMockito.given; |
38 | 43 | import static org.mockito.Mockito.mock; |
39 | 44 |
|
@@ -145,6 +150,27 @@ void consumeWhenTokenDoesNotExistsThenReturnNull() { |
145 | 150 | assertThat(consumedOneTimeToken).isNull(); |
146 | 151 | } |
147 | 152 |
|
| 153 | + @Test |
| 154 | + void consumeWhenTokenIsDeletedConcurrentlyThenReturnNull() throws Exception { |
| 155 | + // Simulates a concurrent consume: SELECT finds the token but DELETE affects |
| 156 | + // 0 rows because another caller already consumed it. |
| 157 | + JdbcOperations jdbcOperations = mock(JdbcOperations.class); |
| 158 | + Instant notExpired = Instant.now().plus(5, ChronoUnit.MINUTES); |
| 159 | + OneTimeToken token = new DefaultOneTimeToken(TOKEN_VALUE, USERNAME, notExpired); |
| 160 | + given(jdbcOperations.query(any(String.class), any(PreparedStatementSetter.class), |
| 161 | + ArgumentMatchers.<RowMapper<OneTimeToken>>any())) |
| 162 | + .willReturn(List.of(token)); |
| 163 | + given(jdbcOperations.update(any(String.class), any(PreparedStatementSetter.class))).willReturn(0); |
| 164 | + JdbcOneTimeTokenService service = new JdbcOneTimeTokenService(jdbcOperations); |
| 165 | + try { |
| 166 | + OneTimeToken consumed = service.consume(new OneTimeTokenAuthenticationToken(TOKEN_VALUE)); |
| 167 | + assertThat(consumed).isNull(); |
| 168 | + } |
| 169 | + finally { |
| 170 | + service.destroy(); |
| 171 | + } |
| 172 | + } |
| 173 | + |
148 | 174 | @Test |
149 | 175 | void consumeWhenTokenIsExpiredThenReturnNull() { |
150 | 176 | GenerateOneTimeTokenRequest request = new GenerateOneTimeTokenRequest(USERNAME); |
|
0 commit comments