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