1
+ package io .github .gunkim .ratelimiter .window ;
2
+
3
+ import org .junit .jupiter .api .DisplayName ;
4
+ import org .junit .jupiter .api .Test ;
5
+
6
+ import java .util .concurrent .CountDownLatch ;
7
+ import java .util .concurrent .TimeUnit ;
8
+ import java .util .concurrent .atomic .AtomicInteger ;
9
+ import java .util .concurrent .atomic .AtomicLong ;
10
+
11
+ import static org .assertj .core .api .Assertions .assertThat ;
12
+ import static org .mockito .Mockito .mock ;
13
+ import static org .mockito .Mockito .times ;
14
+ import static org .mockito .Mockito .verify ;
15
+
16
+ @ DisplayName ("SlidingWindowRateLimiter는" )
17
+ class SlidingWindowRateLimiterTest {
18
+ private static final int WINDOW_SIZE = 1_000 ;
19
+ private static final int REQUESTS_LIMIT = 5 ;
20
+
21
+ @ Test
22
+ void window_size_이내_요청은_처리된다 () {
23
+ SlidingWindowRateLimiter rateLimiter = createRateLimiter ();
24
+ executeAndVerifyRequest (rateLimiter , 1 );
25
+ }
26
+
27
+ @ Test
28
+ void 제한_초과_요청은_처리되지_않는다 () throws InterruptedException {
29
+ SlidingWindowRateLimiter rateLimiter = createRateLimiter ();
30
+ var counter = new AtomicInteger (0 );
31
+ var countDownLatch = new CountDownLatch (REQUESTS_LIMIT );
32
+
33
+ executeBulkRequests (rateLimiter , counter , countDownLatch , REQUESTS_LIMIT + 3 );
34
+
35
+ countDownLatch .await (1 , TimeUnit .SECONDS );
36
+ assertThat (counter .get ()).isEqualTo (REQUESTS_LIMIT );
37
+ }
38
+
39
+ @ Test
40
+ void 슬라이딩_윈도우_동작을_만족한다 () {
41
+ var currentTime = new AtomicLong (0 );
42
+ TimeProvider timeProvider = currentTime ::get ;
43
+ var rateLimiter = new SlidingWindowRateLimiter (WINDOW_SIZE , REQUESTS_LIMIT , timeProvider );
44
+
45
+ int totalRequests = sendAllWindowRequests (rateLimiter , currentTime );
46
+
47
+ assertThat (totalRequests )
48
+ .isGreaterThan (REQUESTS_LIMIT )
49
+ .isLessThanOrEqualTo (REQUESTS_LIMIT * 2 );
50
+ }
51
+
52
+ private int sendAllWindowRequests (SlidingWindowRateLimiter rateLimiter , AtomicLong currentTime ) {
53
+ int firstWindowRequests = sendRequests (rateLimiter , REQUESTS_LIMIT + 2 , currentTime );
54
+ assertThat (firstWindowRequests )
55
+ .as ("임계치에 대한 제한이 정상적으로 구현되었는지 확인해보세요." )
56
+ .isEqualTo (REQUESTS_LIMIT );
57
+
58
+ currentTime .addAndGet (WINDOW_SIZE / 2 );
59
+ int secondWindowRequests = sendRequests (rateLimiter , REQUESTS_LIMIT , currentTime );
60
+ assertThat (secondWindowRequests ).isZero ();
61
+
62
+ currentTime .addAndGet (WINDOW_SIZE / 2 );
63
+ int thirdWindowRequests = sendRequests (rateLimiter , REQUESTS_LIMIT , currentTime );
64
+ assertThat (thirdWindowRequests ).isEqualTo (1 );
65
+
66
+ return firstWindowRequests + secondWindowRequests + thirdWindowRequests ;
67
+ }
68
+
69
+ private int sendRequests (SlidingWindowRateLimiter rateLimiter , int count , AtomicLong currentTime ) {
70
+ var successCount = new AtomicInteger ();
71
+ for (int i = 0 ; i < count ; i ++) {
72
+ rateLimiter .handleRequest (successCount ::getAndIncrement );
73
+ currentTime .addAndGet (1 ); // 각 요청마다 1ms 증가
74
+ }
75
+ return successCount .get ();
76
+ }
77
+
78
+ private void executeBulkRequests (SlidingWindowRateLimiter rateLimiter , AtomicInteger counter , CountDownLatch latch , int count ) {
79
+ for (int i = 0 ; i < count ; i ++) {
80
+ rateLimiter .handleRequest (() -> {
81
+ counter .incrementAndGet ();
82
+ latch .countDown ();
83
+ });
84
+ }
85
+ }
86
+
87
+ private SlidingWindowRateLimiter createRateLimiter () {
88
+ var currentTime = new AtomicLong (0 );
89
+ return new SlidingWindowRateLimiter (WINDOW_SIZE , REQUESTS_LIMIT , currentTime ::get );
90
+ }
91
+
92
+ private void executeAndVerifyRequest (SlidingWindowRateLimiter rateLimiter , int times ) {
93
+ Runnable runnable = mock (Runnable .class );
94
+ rateLimiter .handleRequest (runnable );
95
+ verify (runnable , times (times )).run ();
96
+ }
97
+ }
0 commit comments