add tests

This commit is contained in:
priyank-sriv 2020-05-21 17:36:25 +05:30
parent 8d79b7c5d6
commit dd0a8e2d11
6 changed files with 184 additions and 27 deletions

View File

@ -5,6 +5,7 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
@ -44,10 +45,11 @@ public class RateLimitInterceptor implements HandlerInterceptor {
} else { } 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.addHeader(HEADER_RETRY_AFTER, String.valueOf(waitForRefill));
response.sendError(HttpStatus.TOO_MANY_REQUESTS.value(), "You have exhausted your API Request Quota"); // 429
return false; return false;
} }

View File

@ -5,34 +5,28 @@ import java.time.Duration;
import io.github.bucket4j.Bandwidth; import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Refill; import io.github.bucket4j.Refill;
enum PricingPlan { public enum PricingPlan {
FREE { FREE(20),
@Override BASIC(40),
Bandwidth getLimit() {
return Bandwidth.classic(20, Refill.intervally(20, Duration.ofHours(1)));
}
},
BASIC { PROFESSIONAL(100);;
@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();
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) { static PricingPlan resolvePlanFromApiKey(String apiKey) {
if (apiKey == null || apiKey.isEmpty()) { if (apiKey == null || apiKey.isEmpty()) {
return FREE; return FREE;

View File

@ -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"));
}
}

View File

@ -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"));
}
}

View File

@ -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.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;

View File

@ -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());
}
}