add tests
This commit is contained in:
parent
8d79b7c5d6
commit
dd0a8e2d11
@ -5,6 +5,7 @@ import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
@ -44,10 +45,11 @@ public class RateLimitInterceptor implements HandlerInterceptor {
|
||||
|
||||
} else {
|
||||
|
||||
long waitForRefill = probe.getNanosToWaitForRefill() % 1_000_000_000;
|
||||
long waitForRefill = probe.getNanosToWaitForRefill() / 1_000_000_000;
|
||||
|
||||
response.sendError(HttpStatus.TOO_MANY_REQUESTS.value(), "You have exhausted your API Request Quota"); // 429
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
response.addHeader(HEADER_RETRY_AFTER, String.valueOf(waitForRefill));
|
||||
response.sendError(HttpStatus.TOO_MANY_REQUESTS.value(), "You have exhausted your API Request Quota"); // 429
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -5,34 +5,28 @@ import java.time.Duration;
|
||||
import io.github.bucket4j.Bandwidth;
|
||||
import io.github.bucket4j.Refill;
|
||||
|
||||
enum PricingPlan {
|
||||
public enum PricingPlan {
|
||||
|
||||
FREE {
|
||||
FREE(20),
|
||||
|
||||
@Override
|
||||
Bandwidth getLimit() {
|
||||
return Bandwidth.classic(20, Refill.intervally(20, Duration.ofHours(1)));
|
||||
}
|
||||
},
|
||||
BASIC(40),
|
||||
|
||||
BASIC {
|
||||
|
||||
@Override
|
||||
Bandwidth getLimit() {
|
||||
return Bandwidth.classic(40, Refill.intervally(40, Duration.ofHours(1)));
|
||||
}
|
||||
},
|
||||
|
||||
PROFESSIONAL {
|
||||
|
||||
@Override
|
||||
Bandwidth getLimit() {
|
||||
return Bandwidth.classic(100, Refill.intervally(100, Duration.ofHours(1)));
|
||||
}
|
||||
};
|
||||
|
||||
abstract Bandwidth getLimit();
|
||||
PROFESSIONAL(100);;
|
||||
|
||||
private int bucketCapacity;
|
||||
|
||||
private PricingPlan(int bucketCapacity) {
|
||||
this.bucketCapacity = bucketCapacity;
|
||||
}
|
||||
|
||||
Bandwidth getLimit() {
|
||||
return Bandwidth.classic(bucketCapacity, Refill.intervally(bucketCapacity, Duration.ofHours(1)));
|
||||
}
|
||||
|
||||
public int bucketCapacity() {
|
||||
return bucketCapacity;
|
||||
}
|
||||
|
||||
static PricingPlan resolvePlanFromApiKey(String apiKey) {
|
||||
if (apiKey == null || apiKey.isEmpty()) {
|
||||
return FREE;
|
||||
|
@ -0,0 +1,63 @@
|
||||
package com.baeldung.ratelimiting.bootstarterapp;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.RequestBuilder;
|
||||
|
||||
import com.baeldung.ratelimiting.bucket4japp.service.PricingPlan;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = Bucket4jRateLimitApp.class)
|
||||
@TestPropertySource(properties = "spring.config.location=classpath:ratelimiting/application-bucket4j-starter.yml")
|
||||
@AutoConfigureMockMvc
|
||||
public class Bucket4jBootStarterRateLimitIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Test
|
||||
public void givenTriangleAreaCalculator_whenRequestsWithinRateLimit_thenAccepted() throws Exception {
|
||||
|
||||
RequestBuilder request = post("/api/v1/area/triangle").contentType(MediaType.APPLICATION_JSON_VALUE)
|
||||
.content("{ \"height\": 8, \"base\": 10 }")
|
||||
.header("X-api-key", "FX001-UBSZ5YRYQ");
|
||||
|
||||
for (int i = 1; i <= PricingPlan.FREE.bucketCapacity(); i++) {
|
||||
mockMvc.perform(request)
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(header().exists("X-Rate-Limit-Remaining"))
|
||||
.andExpect(jsonPath("$.shape", equalTo("triangle")))
|
||||
.andExpect(jsonPath("$.area", equalTo(40d)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenTriangleAreaCalculator_whenRequestRateLimitTriggered_thenRejected() throws Exception {
|
||||
|
||||
RequestBuilder request = post("/api/v1/area/triangle").contentType(MediaType.APPLICATION_JSON_VALUE)
|
||||
.content("{ \"height\": 8, \"base\": 10 }")
|
||||
.header("X-api-key", "FX001-ZBSY6YSLP");
|
||||
|
||||
for (int i = 1; i <= PricingPlan.FREE.bucketCapacity(); i++) {
|
||||
mockMvc.perform(request); // exhaust limit
|
||||
}
|
||||
|
||||
mockMvc.perform(request)
|
||||
.andExpect(status().isTooManyRequests())
|
||||
.andExpect(jsonPath("$.message", equalTo("You have exhausted your API Request Quota")))
|
||||
.andExpect(header().exists("X-Rate-Limit-Retry-After-Seconds"));
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package com.baeldung.ratelimiting.bucket4japp;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.RequestBuilder;
|
||||
|
||||
import com.baeldung.ratelimiting.bucket4japp.service.PricingPlan;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = Bucket4jRateLimitApp.class)
|
||||
@AutoConfigureMockMvc
|
||||
public class Bucket4jRateLimitIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Test
|
||||
public void givenRectangleAreaCalculator_whenRequestsWithinRateLimit_thenAccepted() throws Exception {
|
||||
|
||||
RequestBuilder request = post("/api/v1/area/rectangle").contentType(MediaType.APPLICATION_JSON_VALUE)
|
||||
.content("{ \"length\": 12, \"width\": 10 }")
|
||||
.header("X-api-key", "FX001-UBSZ5YRYQ");
|
||||
|
||||
for (int i = 1; i <= PricingPlan.FREE.bucketCapacity(); i++) {
|
||||
mockMvc.perform(request)
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(header().exists("X-Rate-Limit-Remaining"))
|
||||
.andExpect(jsonPath("$.shape", equalTo("rectangle")))
|
||||
.andExpect(jsonPath("$.area", equalTo(120d)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenReactangleAreaCalculator_whenRequestRateLimitTriggered_thenRejected() throws Exception {
|
||||
|
||||
RequestBuilder request = post("/api/v1/area/rectangle").contentType(MediaType.APPLICATION_JSON_VALUE)
|
||||
.content("{ \"length\": 12, \"width\": 10 }")
|
||||
.header("X-api-key", "FX001-ZBSY6YSLP");
|
||||
|
||||
for (int i = 1; i <= PricingPlan.FREE.bucketCapacity(); i++) {
|
||||
mockMvc.perform(request); // exhaust limit
|
||||
}
|
||||
|
||||
mockMvc.perform(request)
|
||||
.andExpect(status().isTooManyRequests())
|
||||
.andExpect(status().reason("You have exhausted your API Request Quota"))
|
||||
.andExpect(header().exists("X-Rate-Limit-Retry-After-Seconds"));
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.baledung.ratelimiting.bucket4j;
|
||||
package com.baeldung.ratelimiting.bucket4japp;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
@ -0,0 +1,36 @@
|
||||
package com.baeldung.ratelimiting.bucket4japp;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.baeldung.ratelimiting.bucket4japp.service.PricingPlan;
|
||||
import com.baeldung.ratelimiting.bucket4japp.service.PricingPlanService;
|
||||
|
||||
import io.github.bucket4j.Bucket;
|
||||
|
||||
public class PricingPlanServiceUnitTest {
|
||||
|
||||
private PricingPlanService service = new PricingPlanService();
|
||||
|
||||
@Test
|
||||
public void givenAPIKey_whenFreePlan_thenReturnFreePlanBucket() {
|
||||
Bucket bucket = service.resolveBucket("FX001-UBSZ5YRYQ");
|
||||
|
||||
assertEquals(PricingPlan.FREE.bucketCapacity(), bucket.getAvailableTokens());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenAPIKey_whenBasiclan_thenReturnBasicPlanBucket() {
|
||||
Bucket bucket = service.resolveBucket("BX001-MBSZ5YRYP");
|
||||
|
||||
assertEquals(PricingPlan.BASIC.bucketCapacity(), bucket.getAvailableTokens());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenAPIKey_whenProfessionalPlan_thenReturnProfessionalPlanBucket() {
|
||||
Bucket bucket = service.resolveBucket("PX001-NBSZ5YRYY");
|
||||
|
||||
assertEquals(PricingPlan.PROFESSIONAL.bucketCapacity(), bucket.getAvailableTokens());
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user