String writeValue(T obj) throws JsonProcessingException {
+ return objectMapper.writeValueAsString(obj);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javaops/bootjava/util/ValidationUtil.java b/src/main/java/ru/javaops/bootjava/util/ValidationUtil.java
new file mode 100644
index 0000000..4d5c29c
--- /dev/null
+++ b/src/main/java/ru/javaops/bootjava/util/ValidationUtil.java
@@ -0,0 +1,24 @@
+package ru.javaops.bootjava.util;
+
+import lombok.experimental.UtilityClass;
+import ru.javaops.bootjava.error.IllegalRequestDataException;
+import ru.javaops.bootjava.model.BaseEntity;
+
+@UtilityClass
+public class ValidationUtil {
+
+ public static void checkNew(BaseEntity entity) {
+ if (!entity.isNew()) {
+ throw new IllegalRequestDataException(entity.getClass().getSimpleName() + " must be new (id=null)");
+ }
+ }
+
+ // Conservative when you reply, but accept liberally (http://stackoverflow.com/a/32728226/548473)
+ public static void assureIdConsistent(BaseEntity entity, int id) {
+ if (entity.isNew()) {
+ entity.setId(id);
+ } else if (entity.id() != id) {
+ throw new IllegalRequestDataException(entity.getClass().getSimpleName() + " must has id=" + id);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javaops/bootjava/web/AccountController.java b/src/main/java/ru/javaops/bootjava/web/AccountController.java
new file mode 100644
index 0000000..a43eb3e
--- /dev/null
+++ b/src/main/java/ru/javaops/bootjava/web/AccountController.java
@@ -0,0 +1,113 @@
+package ru.javaops.bootjava.web;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.CachePut;
+import org.springframework.data.rest.webmvc.RepositoryLinksResource;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.MediaTypes;
+import org.springframework.hateoas.server.RepresentationModelProcessor;
+import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
+import ru.javaops.bootjava.AuthUser;
+import ru.javaops.bootjava.model.Role;
+import ru.javaops.bootjava.model.User;
+import ru.javaops.bootjava.repository.UserRepository;
+import ru.javaops.bootjava.util.ValidationUtil;
+
+import javax.validation.Valid;
+import java.net.URI;
+import java.util.EnumSet;
+
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
+
+/**
+ * Do not use {@link org.springframework.data.rest.webmvc.RepositoryRestController (BasePathAwareController}
+ * Bugs:
+ * NPE with http://localhost:8080/api/account
+ * data.rest.base-path missed in HAL links
+ * Two endpoints created
+ *
+ * RequestMapping("/${spring.data.rest.basePath}/account") give "Not enough variable values"
+ */
+@RestController
+@RequestMapping(AccountController.URL)
+@AllArgsConstructor
+@Slf4j
+@Tag(name = "Account Controller")
+public class AccountController implements RepresentationModelProcessor {
+ static final String URL = "/api/account";
+
+ @SuppressWarnings("unchecked")
+ private static final RepresentationModelAssemblerSupport> ASSEMBLER =
+ new RepresentationModelAssemblerSupport(AccountController.class, (Class>) (Class>) EntityModel.class) {
+ @Override
+ public EntityModel toModel(User user) {
+ return EntityModel.of(user, linkTo(AccountController.class).withSelfRel());
+ }
+ };
+
+ private final UserRepository userRepository;
+
+ @GetMapping(produces = MediaTypes.HAL_JSON_VALUE)
+ public EntityModel get(@AuthenticationPrincipal AuthUser authUser) {
+ log.info("get {}", authUser);
+ return ASSEMBLER.toModel(authUser.getUser());
+ }
+
+ @DeleteMapping
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ @CacheEvict(value = "users", key = "#authUser.username")
+ public void delete(@AuthenticationPrincipal AuthUser authUser) {
+ log.info("delete {}", authUser);
+ userRepository.deleteById(authUser.id());
+ }
+
+ @PostMapping(value = "/register", consumes = MediaType.APPLICATION_JSON_VALUE)
+ @ResponseStatus(value = HttpStatus.CREATED)
+ public ResponseEntity> register(@Valid @RequestBody User user) {
+ log.info("register {}", user);
+ ValidationUtil.checkNew(user);
+ user.setRoles(EnumSet.of(Role.USER));
+ user = userRepository.save(user);
+ URI uriOfNewResource = ServletUriComponentsBuilder.fromCurrentContextPath()
+ .path("/api/account")
+ .build().toUri();
+ return ResponseEntity.created(uriOfNewResource).body(ASSEMBLER.toModel(user));
+ }
+
+ @PutMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ @CachePut(value = "users", key = "#authUser.username")
+ public User update(@Valid @RequestBody User user, @AuthenticationPrincipal AuthUser authUser) {
+ log.info("update {} to {}", authUser, user);
+ User oldUser = authUser.getUser();
+ ValidationUtil.assureIdConsistent(user, oldUser.id());
+ user.setRoles(oldUser.getRoles());
+ if (user.getPassword() == null) {
+ user.setPassword(oldUser.getPassword());
+ }
+ return userRepository.save(user);
+ }
+
+/*
+ @GetMapping(value = "/pageDemo", produces = MediaTypes.HAL_JSON_VALUE)
+ public PagedModel> pageDemo(Pageable page, PagedResourcesAssembler pagedAssembler) {
+ Page users = userRepository.findAll(page);
+ return pagedAssembler.toModel(users, ASSEMBLER);
+ }
+*/
+
+ @Override
+ public RepositoryLinksResource process(RepositoryLinksResource resource) {
+ resource.add(linkTo(AccountController.class).withRel("account"));
+ return resource;
+ }
+}
diff --git a/src/main/java/ru/javaops/bootjava/web/error/GlobalExceptionHandler.java b/src/main/java/ru/javaops/bootjava/web/error/GlobalExceptionHandler.java
new file mode 100644
index 0000000..d441662
--- /dev/null
+++ b/src/main/java/ru/javaops/bootjava/web/error/GlobalExceptionHandler.java
@@ -0,0 +1,38 @@
+package ru.javaops.bootjava.web.error;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.web.servlet.error.ErrorAttributes;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
+import ru.javaops.bootjava.error.AppException;
+
+import java.util.Map;
+
+@RestControllerAdvice
+@AllArgsConstructor
+@Slf4j
+public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
+ private final ErrorAttributes errorAttributes;
+
+ @ExceptionHandler(AppException.class)
+ public ResponseEntity> appException(AppException ex, WebRequest request) {
+ log.error("Application Exception", ex);
+ Map body = errorAttributes.getErrorAttributes(request, ex.getOptions());
+ HttpStatus status = ex.getStatus();
+ body.put("status", status.value());
+ body.put("error", status.getReasonPhrase());
+ return ResponseEntity.status(status).body(body);
+ }
+
+ @Override
+ protected ResponseEntity handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
+ log.error("Exception", ex);
+ return super.handleExceptionInternal(ex, body, headers, status, request);
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
deleted file mode 100644
index e69de29..0000000
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
new file mode 100644
index 0000000..77f94f8
--- /dev/null
+++ b/src/main/resources/application.yaml
@@ -0,0 +1,61 @@
+# https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
+spring:
+ jpa:
+ show-sql: true
+ open-in-view: false
+ hibernate:
+ ddl-auto: create
+ properties:
+ # http://docs.jboss.org/hibernate/orm/5.3/userguide/html_single/Hibernate_User_Guide.html#configurations
+ hibernate:
+ format_sql: true
+ default_batch_fetch_size: 20
+ # https://stackoverflow.com/questions/21257819/what-is-the-difference-between-hibernate-jdbc-fetch-size-and-hibernate-jdbc-batc
+ jdbc.batch_size: 20
+ datasource:
+ # ImMemory
+ url: jdbc:h2:mem:voting
+ # tcp: jdbc:h2:tcp://localhost:9092/mem:voting
+ # Absolute path
+ # url: jdbc:h2:C:/projects/bootjava/restorant-voting/db/voting
+ # tcp: jdbc:h2:tcp://localhost:9092/C:/projects/bootjava/restorant-voting/db/voting
+ # Relative path form current dir
+ # url: jdbc:h2:./db/voting
+ # Relative path from home
+ # url: jdbc:h2:~/voting
+ # tcp: jdbc:h2:tcp://localhost:9092/~/voting
+ username: sa
+ password:
+ h2.console.enabled: true
+
+ data.rest:
+ # https://docs.spring.io/spring-data/rest/docs/current/reference/html/#getting-started.basic-settings
+ basePath: /api
+ defaultPageSize: 20
+ returnBodyOnCreate: true
+
+# https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#security-properties
+# security:
+# user:
+# name: user
+# password: password
+# roles: USER
+
+logging:
+ level:
+ root: WARN
+ ru.javaops.bootjava: DEBUG
+# org.springframework.security.web.FilterChainProxy: DEBUG
+
+server.servlet:
+ encoding:
+ charset: UTF-8 # Charset of HTTP requests and responses. Added to the "Content-Type" header if not set explicitly
+ enabled: true # Enable http encoding support
+ force: true
+
+# Jackson Serialization Issue Resolver
+# jackson:
+# visibility.field: any
+# visibility.getter: none
+# visibility.setter: none
+# visibility.is-getter: none
\ No newline at end of file
diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql
new file mode 100644
index 0000000..778d2f3
--- /dev/null
+++ b/src/main/resources/data.sql
@@ -0,0 +1,8 @@
+INSERT INTO USERS (EMAIL, FIRST_NAME, LAST_NAME, PASSWORD)
+VALUES ('user@gmail.com', 'User_First', 'User_Last', '{noop}password'),
+ ('admin@javaops.ru', 'Admin_First', 'Admin_Last', '{noop}admin');
+
+INSERT INTO USER_ROLE (ROLE, USER_ID)
+VALUES ('USER', 1),
+ ('ADMIN', 2),
+ ('USER', 2);
\ No newline at end of file
diff --git a/src/test/java/ru/javaops/bootjava/RestaurantVotingApplicationTests.java b/src/test/java/ru/javaops/bootjava/RestaurantVotingApplicationTests.java
deleted file mode 100644
index 52bba6d..0000000
--- a/src/test/java/ru/javaops/bootjava/RestaurantVotingApplicationTests.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package ru.javaops.bootjava;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.context.SpringBootTest;
-
-@SpringBootTest
-class RestaurantVotingApplicationTests {
-
- @Test
- void contextLoads() {
- }
-}
diff --git a/src/test/java/ru/javaops/bootjava/UserTestUtil.java b/src/test/java/ru/javaops/bootjava/UserTestUtil.java
new file mode 100644
index 0000000..c0c5c78
--- /dev/null
+++ b/src/test/java/ru/javaops/bootjava/UserTestUtil.java
@@ -0,0 +1,49 @@
+package ru.javaops.bootjava;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.ResultMatcher;
+import ru.javaops.bootjava.model.Role;
+import ru.javaops.bootjava.model.User;
+import ru.javaops.bootjava.util.JsonUtil;
+
+import java.io.UnsupportedEncodingException;
+import java.util.List;
+import java.util.function.BiConsumer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class UserTestUtil {
+ public static final int USER_ID = 1;
+ public static final int ADMIN_ID = 2;
+ public static final String USER_MAIL = "user@gmail.com";
+ public static final String ADMIN_MAIL = "admin@javaops.ru";
+ public static final User user = new User(USER_ID, USER_MAIL, "User_First", "User_Last", "password", List.of(Role.USER));
+ public static final User admin = new User(ADMIN_ID, ADMIN_MAIL, "Admin_First", "Admin_Last", "admin", List.of(Role.ADMIN, Role.USER));
+
+ public static User getNew() {
+ return new User(null, "new@gmail.com", "New_First", "New_Last", "newpass", List.of(Role.USER));
+ }
+
+ public static User getUpdated() {
+ return new User(USER_ID, "user_update@gmail.com", "User_First_Update", "User_Last_Update", "password_update", List.of(Role.USER));
+ }
+
+ public static void assertEquals(User actual, User expected) {
+ assertThat(actual).usingRecursiveComparison().ignoringFields("password").isEqualTo(expected);
+ }
+
+ // No id in HATEOAS answer
+ public static void assertNoIdEquals(User actual, User expected) {
+ assertThat(actual).usingRecursiveComparison().ignoringFields("id", "password").isEqualTo(expected);
+ }
+
+ public static User asUser(MvcResult mvcResult) throws UnsupportedEncodingException, JsonProcessingException {
+ String jsonActual = mvcResult.getResponse().getContentAsString();
+ return JsonUtil.readValue(jsonActual, User.class);
+ }
+
+ public static ResultMatcher jsonMatcher(User expected, BiConsumer equalsAssertion) {
+ return mvcResult -> equalsAssertion.accept(asUser(mvcResult), expected);
+ }
+}
diff --git a/src/test/java/ru/javaops/bootjava/web/AbstractControllerTest.java b/src/test/java/ru/javaops/bootjava/web/AbstractControllerTest.java
new file mode 100644
index 0000000..68cf79c
--- /dev/null
+++ b/src/test/java/ru/javaops/bootjava/web/AbstractControllerTest.java
@@ -0,0 +1,26 @@
+package ru.javaops.bootjava.web;
+
+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.test.context.ActiveProfiles;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+import org.springframework.transaction.annotation.Transactional;
+
+//https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-testing-spring-boot-applications
+@SpringBootTest
+@Transactional
+@AutoConfigureMockMvc
+@ActiveProfiles("test")
+//https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-mock-environment
+public abstract class AbstractControllerTest {
+
+ @Autowired
+ protected MockMvc mockMvc;
+
+ protected ResultActions perform(MockHttpServletRequestBuilder builder) throws Exception {
+ return mockMvc.perform(builder);
+ }
+}
diff --git a/src/test/java/ru/javaops/bootjava/web/AccountControllerTest.java b/src/test/java/ru/javaops/bootjava/web/AccountControllerTest.java
new file mode 100644
index 0000000..9185be8
--- /dev/null
+++ b/src/test/java/ru/javaops/bootjava/web/AccountControllerTest.java
@@ -0,0 +1,75 @@
+package ru.javaops.bootjava.web;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.hateoas.MediaTypes;
+import org.springframework.http.MediaType;
+import org.springframework.security.test.context.support.WithUserDetails;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import ru.javaops.bootjava.UserTestUtil;
+import ru.javaops.bootjava.model.User;
+import ru.javaops.bootjava.repository.UserRepository;
+
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static ru.javaops.bootjava.UserTestUtil.*;
+import static ru.javaops.bootjava.util.JsonUtil.writeValue;
+import static ru.javaops.bootjava.web.AccountController.URL;
+
+class AccountControllerTest extends AbstractControllerTest {
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Test
+ @WithUserDetails(value = USER_MAIL)
+ void get() throws Exception {
+ perform(MockMvcRequestBuilders.get(URL))
+ .andExpect(status().isOk())
+ .andDo(print())
+ .andExpect(content().contentTypeCompatibleWith(MediaTypes.HAL_JSON_VALUE))
+ .andExpect(jsonMatcher(user, UserTestUtil::assertEquals));
+ }
+
+ @Test
+ void getUnAuth() throws Exception {
+ perform(MockMvcRequestBuilders.get(URL))
+ .andExpect(status().isUnauthorized());
+ }
+
+ @Test
+ @WithUserDetails(value = USER_MAIL)
+ void delete() throws Exception {
+ perform(MockMvcRequestBuilders.delete(URL))
+ .andExpect(status().isNoContent());
+ Assertions.assertFalse(userRepository.findById(USER_ID).isPresent());
+ Assertions.assertTrue(userRepository.findById(ADMIN_ID).isPresent());
+ }
+
+ @Test
+ void register() throws Exception {
+ User newUser = UserTestUtil.getNew();
+ User registered = asUser(perform(MockMvcRequestBuilders.post(URL + "/register")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(writeValue(newUser)))
+ .andExpect(status().isCreated()).andReturn());
+ int newId = registered.id();
+ newUser.setId(newId);
+ UserTestUtil.assertEquals(registered, newUser);
+ UserTestUtil.assertEquals(registered, userRepository.findById(newId).orElseThrow());
+ }
+
+ @Test
+ @WithUserDetails(value = USER_MAIL)
+ void update() throws Exception {
+ User updated = UserTestUtil.getUpdated();
+ perform(MockMvcRequestBuilders.put(URL)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(writeValue(updated)))
+ .andDo(print())
+ .andExpect(status().isNoContent());
+ UserTestUtil.assertEquals(updated, userRepository.findById(USER_ID).orElseThrow());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/javaops/bootjava/web/UserControllerTest.java b/src/test/java/ru/javaops/bootjava/web/UserControllerTest.java
new file mode 100644
index 0000000..9295ca0
--- /dev/null
+++ b/src/test/java/ru/javaops/bootjava/web/UserControllerTest.java
@@ -0,0 +1,93 @@
+package ru.javaops.bootjava.web;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.hateoas.MediaTypes;
+import org.springframework.http.MediaType;
+import org.springframework.security.test.context.support.WithUserDetails;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import ru.javaops.bootjava.UserTestUtil;
+import ru.javaops.bootjava.model.User;
+import ru.javaops.bootjava.repository.UserRepository;
+
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static ru.javaops.bootjava.UserTestUtil.*;
+import static ru.javaops.bootjava.util.JsonUtil.writeValue;
+
+class UserControllerTest extends AbstractControllerTest {
+ static final String URL = "/api/users/";
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Test
+ @WithUserDetails(value = ADMIN_MAIL)
+ void get() throws Exception {
+ perform(MockMvcRequestBuilders.get(URL + USER_ID))
+ .andExpect(status().isOk())
+ .andDo(print())
+ .andExpect(content().contentTypeCompatibleWith(MediaTypes.HAL_JSON_VALUE))
+ .andExpect(jsonMatcher(user, UserTestUtil::assertNoIdEquals));
+ }
+
+ @Test
+ @WithUserDetails(value = ADMIN_MAIL)
+ void getAll() throws Exception {
+ // TODO check content yourself
+ perform(MockMvcRequestBuilders.get(URL))
+ .andExpect(status().isOk())
+ .andDo(print())
+ .andExpect(content().contentTypeCompatibleWith(MediaTypes.HAL_JSON_VALUE));
+ }
+
+ @Test
+ @WithUserDetails(value = ADMIN_MAIL)
+ void getByEmail() throws Exception {
+ perform(MockMvcRequestBuilders.get(URL + "search/by-email?email=" + ADMIN_MAIL))
+ .andExpect(status().isOk())
+ .andDo(print())
+ .andExpect(content().contentTypeCompatibleWith(MediaTypes.HAL_JSON_VALUE))
+ .andExpect(jsonMatcher(admin, UserTestUtil::assertNoIdEquals));
+ }
+
+ @Test
+ @WithUserDetails(value = USER_MAIL)
+ void getForbidden() throws Exception {
+ perform(MockMvcRequestBuilders.get(URL))
+ .andExpect(status().isForbidden());
+ }
+
+ @Test
+ @WithUserDetails(value = ADMIN_MAIL)
+ void delete() throws Exception {
+ perform(MockMvcRequestBuilders.delete(URL + USER_ID))
+ .andExpect(status().isNoContent());
+ Assertions.assertFalse(userRepository.findById(USER_ID).isPresent());
+ Assertions.assertTrue(userRepository.findById(ADMIN_ID).isPresent());
+ }
+
+ @Test
+ @WithUserDetails(value = ADMIN_MAIL)
+ void create() throws Exception {
+ User newUser = UserTestUtil.getNew();
+ perform(MockMvcRequestBuilders.post(URL)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(writeValue(newUser)))
+ .andExpect(status().isCreated())
+ .andExpect(jsonMatcher(newUser, UserTestUtil::assertNoIdEquals));
+ }
+
+ @Test
+ @WithUserDetails(value = ADMIN_MAIL)
+ void update() throws Exception {
+ User updated = UserTestUtil.getUpdated();
+ perform(MockMvcRequestBuilders.put(URL + USER_ID)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(writeValue(updated)))
+ .andExpect(status().isNoContent());
+ UserTestUtil.assertEquals(updated, userRepository.findById(USER_ID).orElseThrow());
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/application-test.yaml b/src/test/resources/application-test.yaml
new file mode 100644
index 0000000..be16632
--- /dev/null
+++ b/src/test/resources/application-test.yaml
@@ -0,0 +1 @@
+spring.cache.type: none
\ No newline at end of file