This commit is contained in:
Jonathan Cook
2020-05-29 09:52:21 +02:00
55 changed files with 1605 additions and 26 deletions
@@ -0,0 +1,36 @@
package com.baeldung.inttoenum;
import java.util.HashMap;
import java.util.Map;
public enum PizzaStatus {
ORDERED(5),
READY(2),
DELIVERED(0);
private int timeToDelivery;
PizzaStatus(int timeToDelivery) {
this.timeToDelivery = timeToDelivery;
}
public int getTimeToDelivery() {
return timeToDelivery;
}
private static Map<Integer, PizzaStatus> timeToDeliveryToEnumValuesMapping = new HashMap<>();
static {
PizzaStatus[] pizzaStatuses = PizzaStatus.values();
for (int pizzaStatusIndex = 0; pizzaStatusIndex < pizzaStatuses.length; pizzaStatusIndex++) {
timeToDeliveryToEnumValuesMapping.put(
pizzaStatuses[pizzaStatusIndex].getTimeToDelivery(),
pizzaStatuses[pizzaStatusIndex]
);
}
}
public static PizzaStatus castIntToEnum(int timeToDelivery) {
return timeToDeliveryToEnumValuesMapping.get(timeToDelivery);
}
}
@@ -0,0 +1,27 @@
package com.baeldung.inttoenum;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class IntToEnumUnitTest {
@Test
public void whenIntToEnumUsingValuesMethod_thenReturnEnumObject() {
int timeToDeliveryForOrderedPizzaStatus = 5;
PizzaStatus[] pizzaStatuses = PizzaStatus.values();
PizzaStatus pizzaOrderedStatus = null;
for (int pizzaStatusIndex = 0; pizzaStatusIndex < pizzaStatuses.length; pizzaStatusIndex++) {
if (pizzaStatuses[pizzaStatusIndex].getTimeToDelivery() == timeToDeliveryForOrderedPizzaStatus) {
pizzaOrderedStatus = pizzaStatuses[pizzaStatusIndex];
}
}
assertEquals(pizzaOrderedStatus, PizzaStatus.ORDERED);
}
@Test
public void whenIntToEnumUsingMap_thenReturnEnumObject() {
int timeToDeliveryForOrderedPizzaStatus = 5;
assertEquals(PizzaStatus.castIntToEnum(timeToDeliveryForOrderedPizzaStatus), PizzaStatus.ORDERED);
}
}
@@ -19,9 +19,9 @@ class StringComparisonUnitTest {
fun `compare using referential equals operator`() {
val first = "kotlin"
val second = "kotlin"
val copyOfFirst = buildString { "kotlin" }
val third = String("kotlin".toCharArray())
assertTrue { first === second }
assertFalse { first === copyOfFirst }
assertFalse { first === third }
}
@Test
+3 -1
View File
@@ -221,6 +221,7 @@
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<jboss.home>${project.basedir}/target/wildfly-${wildfly.version}</jboss.home>
<jboss.http.port>8756</jboss.http.port>
<module.path>${project.basedir}/target/wildfly-${wildfly.version}/modules</module.path>
</systemPropertyVariables>
<redirectTestOutputToFile>false</redirectTestOutputToFile>
@@ -278,9 +279,10 @@
<arquillian-drone-bom.version>2.0.1.Final</arquillian-drone-bom.version>
<arquillian-rest-client.version>1.0.0.Alpha4</arquillian-rest-client.version>
<logback.version>1.1.7</logback.version>
<resteasy.version>3.8.0.Final</resteasy.version>
<shrinkwrap.version>3.1.3</shrinkwrap.version>
</properties>
</project>
@@ -7,7 +7,7 @@
<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
<class>com.enpy.entity.Student</class>
<class>com.baeldung.jeekotlin.entity.Student</class>
<properties>
<property name="hibernate.hbm2ddl.auto" value="create"/>
@@ -13,7 +13,7 @@ import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
public class HibernateTypesIntegrationTest {
public class HibernateTypesLiveTest {
@Autowired
AlbumRepository albumRepository;
@@ -3,6 +3,6 @@
- [Using JDBI with Spring Boot](https://www.baeldung.com/spring-boot-jdbi)
- [Configuring a Tomcat Connection Pool in Spring Boot](https://www.baeldung.com/spring-boot-tomcat-connection-pool)
- [Integrating Spring Boot with HSQLDB](https://www.baeldung.com/spring-boot-hsqldb)
- [List of In-Memory Databases](http://www.baeldung.com/java-in-memory-databases)
- [List of In-Memory Databases](https://www.baeldung.com/java-in-memory-databases)
- [Oracle Connection Pooling With Spring](https://www.baeldung.com/spring-oracle-connection-pooling)
- More articles: [[<-- prev]](../spring-boot-persistence)
@@ -4,6 +4,7 @@ import javax.sql.DataSource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import com.baeldung.tomcatconnectionpool.application.SpringBootConsoleApplication;
@@ -13,6 +14,7 @@ import org.springframework.boot.test.context.SpringBootTest;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {SpringBootConsoleApplication.class})
@TestPropertySource(properties = "spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource")
public class SpringBootTomcatConnectionPoolIntegrationTest {
@Autowired
@@ -35,23 +35,21 @@
<version>${lombok.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>db-util</artifactId>
<version>${db-util.version}</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>${byte-buddy.version}</version>
</dependency>
</dependencies>
<properties>
<!-- The main class to start by executing java -jar -->
<start-class>com.baeldung.h2db.demo.server.SpringBootApp</start-class>
<spring-boot.version>2.0.4.RELEASE</spring-boot.version>
<hibernate.version>5.3.11.Final</hibernate.version>
<db-util.version>1.0.4</db-util.version>
</properties>
@@ -8,5 +8,4 @@ spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.validator.apply_to_ddl=false
#spring.jpa.properties.hibernate.check_nullability=true
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
debug=true
spring.h2.console.path=/h2-console
@@ -1,8 +1,8 @@
### Relevant Articles:
- [Spring Boot with Multiple SQL Import Files](http://www.baeldung.com/spring-boot-sql-import-files)
- [Configuring Separate Spring DataSource for Tests](http://www.baeldung.com/spring-testing-separate-data-source)
- [Quick Guide on Loading Initial Data with Spring Boot](http://www.baeldung.com/spring-boot-data-sql-and-schema-sql)
- [Spring Boot with Multiple SQL Import Files](https://www.baeldung.com/spring-boot-sql-import-files)
- [Configuring Separate Spring DataSource for Tests](https://www.baeldung.com/spring-testing-separate-data-source)
- [Quick Guide on Loading Initial Data with Spring Boot](https://www.baeldung.com/spring-boot-data-sql-and-schema-sql)
- [Configuring a DataSource Programmatically in Spring Boot](https://www.baeldung.com/spring-boot-configure-data-source-programmatic)
- [Resolving “Failed to Configure a DataSource” Error](https://www.baeldung.com/spring-boot-failed-to-configure-data-source)
- [Hibernate Field Naming with Spring Boot](https://www.baeldung.com/hibernate-field-naming-spring-boot)
@@ -87,7 +87,35 @@
<artifactId>javase</artifactId>
<version>${zxing.version}</version>
</dependency>
<!-- Bucket4j -->
<dependency>
<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-core</artifactId>
<version>${bucket4j.version}</version>
</dependency>
<dependency>
<groupId>com.giffing.bucket4j.spring.boot.starter</groupId>
<artifactId>bucket4j-spring-boot-starter</artifactId>
<version>${bucket4j-spring-boot-starter.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>${caffeine.version}</version>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>jcache</artifactId>
<version>${caffeine.version}</version>
</dependency>
</dependencies>
<repositories>
@@ -200,6 +228,9 @@
<barcode4j.version>2.1</barcode4j.version>
<qrgen.version>2.6.0</qrgen.version>
<zxing.version>3.3.0</zxing.version>
<bucket4j.version>4.10.0</bucket4j.version>
<bucket4j-spring-boot-starter.version>0.2.0</bucket4j-spring-boot-starter.version>
<caffeine.version>2.8.2</caffeine.version>
</properties>
</project>
@@ -0,0 +1,21 @@
package com.baeldung.ratelimiting.bootstarterapp;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication(scanBasePackages = "com.baeldung.ratelimiting", exclude = {
DataSourceAutoConfiguration.class,
SecurityAutoConfiguration.class,
})
@EnableCaching
public class Bucket4jRateLimitApp {
public static void main(String[] args) {
new SpringApplicationBuilder(Bucket4jRateLimitApp.class)
.properties("spring.config.location=classpath:ratelimiting/application-bucket4j-starter.yml")
.run(args);
}
}
@@ -0,0 +1,35 @@
package com.baeldung.ratelimiting.bucket4japp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.baeldung.ratelimiting.bucket4japp.interceptor.RateLimitInterceptor;
@SpringBootApplication(scanBasePackages = "com.baeldung.ratelimiting", exclude = {
DataSourceAutoConfiguration.class,
SecurityAutoConfiguration.class
})
public class Bucket4jRateLimitApp implements WebMvcConfigurer {
@Autowired
@Lazy
private RateLimitInterceptor interceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor)
.addPathPatterns("/api/v1/area/**");
}
public static void main(String[] args) {
new SpringApplicationBuilder(Bucket4jRateLimitApp.class)
.properties("spring.config.location=classpath:ratelimiting/application-bucket4j.yml")
.run(args);
}
}
@@ -0,0 +1,57 @@
package com.baeldung.ratelimiting.bucket4japp.interceptor;
import javax.servlet.http.HttpServletRequest;
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;
import com.baeldung.ratelimiting.bucket4japp.service.PricingPlanService;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.ConsumptionProbe;
@Component
public class RateLimitInterceptor implements HandlerInterceptor {
private static final String HEADER_API_KEY = "X-api-key";
private static final String HEADER_LIMIT_REMAINING = "X-Rate-Limit-Remaining";
private static final String HEADER_RETRY_AFTER = "X-Rate-Limit-Retry-After-Seconds";
@Autowired
private PricingPlanService pricingPlanService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String apiKey = request.getHeader(HEADER_API_KEY);
if (apiKey == null || apiKey.isEmpty()) {
response.sendError(HttpStatus.BAD_REQUEST.value(), "Missing Header: " + HEADER_API_KEY);
return false;
}
Bucket tokenBucket = pricingPlanService.resolveBucket(apiKey);
ConsumptionProbe probe = tokenBucket.tryConsumeAndReturnRemaining(1);
if (probe.isConsumed()) {
response.addHeader(HEADER_LIMIT_REMAINING, String.valueOf(probe.getRemainingTokens()));
return true;
} else {
long waitForRefill = probe.getNanosToWaitForRefill() / 1_000_000_000;
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;
}
}
}
@@ -0,0 +1,42 @@
package com.baeldung.ratelimiting.bucket4japp.service;
import java.time.Duration;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Refill;
public enum PricingPlan {
FREE(20),
BASIC(40),
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;
} else if (apiKey.startsWith("PX001-")) {
return PROFESSIONAL;
} else if (apiKey.startsWith("BX001-")) {
return BASIC;
}
return FREE;
}
}
@@ -0,0 +1,31 @@
package com.baeldung.ratelimiting.bucket4japp.service;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.stereotype.Service;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.Bucket4j;
@Service
public class PricingPlanService {
private final Map<String, Bucket> cache = new ConcurrentHashMap<>();
public Bucket resolveBucket(String apiKey) {
return cache.computeIfAbsent(apiKey, this::newBucket);
}
private Bucket newBucket(String apiKey) {
PricingPlan pricingPlan = PricingPlan.resolvePlanFromApiKey(apiKey);
return bucket(pricingPlan.getLimit());
}
private Bucket bucket(Bandwidth limit) {
return Bucket4j.builder()
.addLimit(limit)
.build();
}
}
@@ -0,0 +1,29 @@
package com.baeldung.ratelimiting.controller;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.baeldung.ratelimiting.dto.AreaV1;
import com.baeldung.ratelimiting.dto.RectangleDimensionsV1;
import com.baeldung.ratelimiting.dto.TriangleDimensionsV1;
@RestController
@RequestMapping(value = "/api/v1/area", consumes = MediaType.APPLICATION_JSON_VALUE)
class AreaCalculationController {
@PostMapping(value = "/rectangle", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<AreaV1> rectangle(@RequestBody RectangleDimensionsV1 dimensions) {
return ResponseEntity.ok(new AreaV1("rectangle", dimensions.getLength() * dimensions.getWidth()));
}
@PostMapping(value = "/triangle", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<AreaV1> triangle(@RequestBody TriangleDimensionsV1 dimensions) {
return ResponseEntity.ok(new AreaV1("triangle", 0.5d * dimensions.getHeight() * dimensions.getBase()));
}
}
@@ -0,0 +1,20 @@
package com.baeldung.ratelimiting.dto;
public class AreaV1 {
private String shape;
private Double area;
public AreaV1(String shape, Double area) {
this.area = area;
this.shape = shape;
}
public Double getArea() {
return area;
}
public String getShape() {
return shape;
}
}
@@ -0,0 +1,15 @@
package com.baeldung.ratelimiting.dto;
public class RectangleDimensionsV1 {
private double length;
private double width;
public double getLength() {
return length;
}
public double getWidth() {
return width;
}
}
@@ -0,0 +1,15 @@
package com.baeldung.ratelimiting.dto;
public class TriangleDimensionsV1 {
private double base;
private double height;
public double getBase() {
return base;
}
public double getHeight() {
return height;
}
}
@@ -0,0 +1,40 @@
server:
port: 9001
spring:
application:
name: bucket4j-starter-api-rate-limit-app
mvc:
throw-exception-if-no-handler-found: true
resources:
add-mappings: false
cache:
cache-names:
- rate-limit-buckets
caffeine:
spec: maximumSize=100000,expireAfterAccess=3600s
bucket4j:
enabled: true
filters:
- cache-name: rate-limit-buckets
url: /api/v1/area.*
http-response-body: "{ \"status\": 429, \"error\": \"Too Many Requests\", \"message\": \"You have exhausted your API Request Quota\" }"
rate-limits:
- expression: "getHeader('X-api-key')"
execute-condition: "getHeader('X-api-key').startsWith('PX001-')"
bandwidths:
- capacity: 100
time: 1
unit: hours
- expression: "getHeader('X-api-key')"
execute-condition: "getHeader('X-api-key').startsWith('BX001-')"
bandwidths:
- capacity: 40
time: 1
unit: hours
- expression: "getHeader('X-api-key')"
bandwidths:
- capacity: 20
time: 1
unit: hours
@@ -0,0 +1,10 @@
server:
port: 9000
spring:
application:
name: bucket4j-api-rate-limit-app
mvc:
throw-exception-if-no-handler-found: true
resources:
add-mappings: false
@@ -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,61 @@
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.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"));
}
}
@@ -0,0 +1,82 @@
package com.baeldung.ratelimiting.bucket4japp;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.time.Duration;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.Bucket4j;
import io.github.bucket4j.Refill;
public class Bucket4jUsageUnitTest {
@Test
public void givenBucketLimit_whenExceedLimit_thenConsumeReturnsFalse() {
Refill refill = Refill.intervally(10, Duration.ofMinutes(1));
Bandwidth limit = Bandwidth.classic(10, refill);
Bucket bucket = Bucket4j.builder()
.addLimit(limit)
.build();
for (int i = 1; i <= 10; i++) {
assertTrue(bucket.tryConsume(1));
}
assertFalse(bucket.tryConsume(1));
}
@Test
public void givenMultipletLimits_whenExceedSmallerLimit_thenConsumeReturnsFalse() {
Bucket bucket = Bucket4j.builder()
.addLimit(Bandwidth.classic(10, Refill.intervally(10, Duration.ofMinutes(1))))
.addLimit(Bandwidth.classic(5, Refill.intervally(5, Duration.ofSeconds(20))))
.build();
for (int i = 1; i <= 5; i++) {
assertTrue(bucket.tryConsume(1));
}
assertFalse(bucket.tryConsume(1));
}
@Test
public void givenBucketLimit_whenThrottleRequests_thenConsumeReturnsTrue() throws InterruptedException {
Refill refill = Refill.intervally(1, Duration.ofSeconds(2));
Bandwidth limit = Bandwidth.classic(1, refill);
Bucket bucket = Bucket4j.builder()
.addLimit(limit)
.build();
assertTrue(bucket.tryConsume(1));
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
CountDownLatch latch = new CountDownLatch(1);
executor.schedule(new AssertTryConsume(bucket, latch), 2, TimeUnit.SECONDS);
latch.await();
}
static class AssertTryConsume implements Runnable {
private Bucket bucket;
private CountDownLatch latch;
AssertTryConsume(Bucket bucket, CountDownLatch latch) {
this.bucket = bucket;
this.latch = latch;
}
@Override
public void run() {
assertTrue(bucket.tryConsume(1));
latch.countDown();
}
}
}
@@ -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());
}
}
@@ -75,7 +75,6 @@
</build>
<properties>
<spring-boot.version>2.1.1.RELEASE</spring-boot.version>
<start-class>com.baeldung.birt.engine.ReportEngineApplication</start-class>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
@@ -0,0 +1,13 @@
# Logs
logs
*.log
# Git
.git
.cache
# Classes
**/*.class
# Ignore md files
*.md
@@ -0,0 +1,10 @@
FROM maven:3.6.0-jdk-11
WORKDIR /code/spring-boot-modules/spring-boot-properties/
COPY ./spring-boot-modules/spring-boot-properties/pom.xml .
COPY ./spring-boot-modules/spring-boot-properties/src ./src
COPY ./parent-boot-2/pom.xml /code/parent-boot-2/pom.xml
COPY ./pom.xml /code/pom.xml
COPY ./custom-pmd-0.0.1.jar /code/custom-pmd-0.0.1.jar
COPY ./baeldung-pmd-rules.xml /code/baeldung-pmd-rules.xml
RUN mvn dependency:resolve
CMD ["mvn", "spring-boot:run"]

Some files were not shown because too many files have changed in this diff Show More