Initial API backbone

This commit is contained in:
droideparanoico
2022-10-08 12:27:21 +02:00
parent 6bd3921714
commit 63fb2fd1c6
31 changed files with 692 additions and 142 deletions

View File

@@ -25,7 +25,7 @@ jobs:
if [ ! -z "${{ secrets.SONAR_TOKEN }}" ] && [ ! -z "${{ secrets.SONAR_HOST_URL }}" ]; then
echo "::set-output name=ok::true"
fi
sonarqube:
needs:
- secrets-gate

View File

@@ -9,4 +9,4 @@ up:
test:
docker-compose run --rm --no-deps -p "8080:8080" java-skeleton-api gradle test
coverage: test
coverage: test

View File

@@ -23,6 +23,8 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation 'com.h2database:h2:2.1.212'
@@ -92,4 +94,4 @@ tasks.withType(Test) {
}
}
}
}
}

View File

@@ -21,6 +21,8 @@ services:
restart: on-failure
volumes:
- rv-volume:/var/lib/postgresql/data
ports:
- "5432:5432"
env_file:
- postgres.dev.env
networks:

View File

@@ -1,4 +1,4 @@
POSTGRES_NAME=postgres-skeleton-db
POSTGRES_DB=postgres_rv_database
POSTGRES_USER=rv_user
POSTGRES_PASSWORD=rv_password
POSTGRES_PASSWORD=rv_password

View File

@@ -0,0 +1,18 @@
package com.rviewer.skeletons.domain.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.With;
import java.util.List;
import java.util.UUID;
@Data
@AllArgsConstructor
@NoArgsConstructor
@With
public class Cart {
private UUID id;
private List<Item> items;
}

View File

@@ -0,0 +1,27 @@
package com.rviewer.skeletons.domain.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.With;
import org.springframework.format.annotation.NumberFormat;
import javax.validation.constraints.NotNull;
import java.util.UUID;
@Data
@AllArgsConstructor
@NoArgsConstructor
@With
public class Item {
@NotNull
private UUID id;
@NotNull
private String name;
@NotNull
@NumberFormat
private Integer quantity;
@NotNull
@NumberFormat
private Float price;
}

View File

@@ -0,0 +1,11 @@
package com.rviewer.skeletons.domain.ports.primary;
import com.rviewer.skeletons.domain.model.Cart;
import java.util.Optional;
public interface CartServicePort {
Optional<Cart> get(String id);
void save(Cart cart);
void delete(String id);
}

View File

@@ -0,0 +1,12 @@
package com.rviewer.skeletons.domain.ports.secondary;
import com.rviewer.skeletons.domain.model.Cart;
import java.util.Optional;
public interface DatabasePort {
Optional<Cart> get(String id);
void save(Cart cart);
void delete(String id);
}

View File

@@ -1,12 +0,0 @@
package com.rviewer.skeletons.domain.responses;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public class PongResponse {
private String message;
private int number;
}

View File

@@ -0,0 +1,34 @@
package com.rviewer.skeletons.domain.service;
import com.rviewer.skeletons.domain.model.Cart;
import com.rviewer.skeletons.domain.ports.primary.CartServicePort;
import com.rviewer.skeletons.domain.ports.secondary.DatabasePort;
import org.springframework.stereotype.Component;
import javax.transaction.Transactional;
import java.util.Optional;
@Component
@Transactional
public class CartService implements CartServicePort {
private final DatabasePort databasePort;
public CartService(DatabasePort databasePort) {
this.databasePort = databasePort;
}
@Override
public Optional<Cart> get(String id) {
return databasePort.get(id);
}
@Override
public void save(Cart cart) {
databasePort.save(cart);
}
@Override
public void delete(String id) {
databasePort.delete(id);
}
}

View File

@@ -1,17 +0,0 @@
package com.rviewer.skeletons.domain.services;
import com.rviewer.skeletons.domain.responses.PongResponse;
import com.rviewer.skeletons.domain.services.persistence.DatabaseConnector;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class PongService {
@Autowired
DatabaseConnector postgresConnector;
public PongResponse getPong() {
return new PongResponse("pong", postgresConnector.getConnectionStatus());
}
}

View File

@@ -1,6 +0,0 @@
package com.rviewer.skeletons.domain.services.persistence;
public interface DatabaseConnector {
public int getConnectionStatus();
}

View File

@@ -1,21 +0,0 @@
package com.rviewer.skeletons.infrastructure.controllers;
import com.rviewer.skeletons.domain.services.PongService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/")
public class PingController {
@Autowired
private PongService pongService;
@GetMapping("/ping")
public ResponseEntity getPing() {
return ResponseEntity.ok(pongService.getPong());
}
}

View File

@@ -0,0 +1,70 @@
package com.rviewer.skeletons.infrastructure.inbound.api.adapter;
import com.rviewer.skeletons.domain.model.Cart;
import com.rviewer.skeletons.domain.model.Item;
import com.rviewer.skeletons.domain.service.CartService;
import com.rviewer.skeletons.infrastructure.inbound.api.exception.CartAlreadyExistsException;
import com.rviewer.skeletons.infrastructure.inbound.api.exception.CartNotFoundException;
import com.rviewer.skeletons.infrastructure.inbound.api.request.CreateCartReq;
import com.rviewer.skeletons.infrastructure.inbound.api.request.UpdateCartReq;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
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 javax.validation.Valid;
import java.util.UUID;
@RestController
@RequestMapping("/carts")
public class CartController {
private final CartService cartService;
public CartController(CartService cartService) {
this.cartService = cartService;
}
@GetMapping("/{id}")
public ResponseEntity<Cart> getCart(@PathVariable String id) {
return ResponseEntity.status(HttpStatus.OK).body(cartService.get(id).orElseThrow(CartNotFoundException::new));
}
@PostMapping("/{id}")
public ResponseEntity<HttpStatus> saveCart(@PathVariable String id, @Valid @RequestBody CreateCartReq createCartReq) {
if (cartService.get(id).isPresent()) {
throw new CartAlreadyExistsException();
} else {
cartService.save(new Cart(UUID.fromString(id), createCartReq.getItems()
.stream()
.map(item -> new Item(item.getId(), item.getName(), item.getQuantity(), item.getPrice()))
.toList()));
return ResponseEntity.status(HttpStatus.CREATED).build();
}
}
@PatchMapping("/{id}")
public ResponseEntity<HttpStatus> updateCart(@PathVariable String id, @Valid @RequestBody UpdateCartReq updateCartReq) {
Cart cart = cartService.get(id).orElseThrow(CartNotFoundException::new);
cart.setItems(updateCartReq.getItems());
cartService.save(cart);
return ResponseEntity.status(HttpStatus.OK).build();
}
@DeleteMapping("/{id}")
public ResponseEntity<HttpStatus> deleteCart(@PathVariable String id) {
if (cartService.get(id).isEmpty()) {
throw new CartNotFoundException();
} else {
cartService.delete(id);
return ResponseEntity.status(HttpStatus.OK).build();
}
}
}

View File

@@ -0,0 +1,13 @@
package com.rviewer.skeletons.infrastructure.inbound.api.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.CONFLICT)
public class CartAlreadyExistsException extends RuntimeException {
private static final String CART_ALREADY_EXISTS = "Invalid identifier.";
public CartAlreadyExistsException() {
super(CART_ALREADY_EXISTS);
}
}

View File

@@ -0,0 +1,13 @@
package com.rviewer.skeletons.infrastructure.inbound.api.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class CartNotFoundException extends RuntimeException {
private static final String CART_NOT_FOUND = "Cart not found for the given ID";
public CartNotFoundException() {
super(CART_NOT_FOUND);
}
}

View File

@@ -0,0 +1,32 @@
package com.rviewer.skeletons.infrastructure.inbound.api.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
@ControllerAdvice
public class ControllerExceptionHandler {
public static final String INVALID_BODY = "Invalid body provided, check the payload.";
@ExceptionHandler(CartNotFoundException.class)
public ResponseEntity<Object> handleCartNotFound(CartNotFoundException e) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.NOT_FOUND);
}
@ExceptionHandler(CartAlreadyExistsException.class)
public ResponseEntity<Object> handleCartAlreadyExists(CartAlreadyExistsException e) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.CONFLICT);
}
@ExceptionHandler({MethodArgumentNotValidException.class, HttpMessageNotReadableException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<Object> handleInvalidBody() {
return new ResponseEntity<>(INVALID_BODY, HttpStatus.BAD_REQUEST);
}
}

View File

@@ -0,0 +1,13 @@
package com.rviewer.skeletons.infrastructure.inbound.api.request;
import com.rviewer.skeletons.domain.model.Item;
import lombok.Getter;
import javax.validation.constraints.NotNull;
import java.util.List;
@Getter
public class CreateCartReq {
@NotNull
private List<Item> items;
}

View File

@@ -0,0 +1,13 @@
package com.rviewer.skeletons.infrastructure.inbound.api.request;
import com.rviewer.skeletons.domain.model.Item;
import lombok.Getter;
import javax.validation.constraints.NotNull;
import java.util.List;
@Getter
public class UpdateCartReq {
@NotNull
private List<Item> items;
}

View File

@@ -0,0 +1,35 @@
package com.rviewer.skeletons.infrastructure.outbound.database.adapter;
import com.rviewer.skeletons.domain.model.Cart;
import com.rviewer.skeletons.domain.ports.secondary.DatabasePort;
import com.rviewer.skeletons.infrastructure.outbound.database.repository.PostgresCartRepository;
import com.rviewer.skeletons.infrastructure.outbound.database.dto.PostgresCart;
import org.springframework.stereotype.Component;
import java.util.Optional;
import java.util.UUID;
@Component
public class PostgresAdapter implements DatabasePort {
private final PostgresCartRepository postgresCartRepository;
public PostgresAdapter(PostgresCartRepository postgresCartRepository) {
this.postgresCartRepository = postgresCartRepository;
}
@Override
public Optional<Cart> get(String id) {
return postgresCartRepository.findById(UUID.fromString(id)).map(PostgresCart::toDomain);
}
@Override
public void save(Cart cart) {
postgresCartRepository.save(PostgresCart.fromDomain(cart));
}
@Override
public void delete(String id) {
postgresCartRepository.deleteById(UUID.fromString(id));
}
}

View File

@@ -0,0 +1,42 @@
package com.rviewer.skeletons.infrastructure.outbound.database.dto;
import com.rviewer.skeletons.domain.model.Cart;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.With;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import java.util.List;
import java.util.UUID;
@Entity
@AllArgsConstructor
@NoArgsConstructor
@With
public class PostgresCart {
@Id
private UUID id;
@OneToMany(cascade= CascadeType.ALL)
private List<PostgresItem> postgresItems;
public static PostgresCart fromDomain(Cart cart) {
return new PostgresCart()
.withId(cart.getId())
.withPostgresItems(cart.getItems()
.stream()
.map(PostgresItem::fromDomain)
.toList());
}
public Cart toDomain() {
return new Cart()
.withId(id)
.withItems(postgresItems
.stream()
.map(PostgresItem::toDomain)
.toList());
}
}

View File

@@ -0,0 +1,38 @@
package com.rviewer.skeletons.infrastructure.outbound.database.dto;
import com.rviewer.skeletons.domain.model.Item;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.With;
import javax.persistence.Entity;
import javax.persistence.Id;
import java.util.UUID;
@Entity
@AllArgsConstructor
@NoArgsConstructor
@With
public class PostgresItem {
@Id
private UUID id;
private String name;
private Integer quantity;
private Float price;
public static PostgresItem fromDomain(Item item) {
return new PostgresItem()
.withId(item.getId())
.withName(item.getName())
.withQuantity(item.getQuantity())
.withPrice(item.getPrice());
}
public Item toDomain() {
return new Item()
.withId(id)
.withName(name)
.withQuantity(quantity)
.withPrice(price);
}
}

View File

@@ -0,0 +1,14 @@
package com.rviewer.skeletons.infrastructure.outbound.database.repository;
import com.rviewer.skeletons.infrastructure.outbound.database.dto.PostgresCart;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
import java.util.UUID;
@Repository
public interface PostgresCartRepository extends CrudRepository<PostgresCart, Long> {
Optional<PostgresCart> findById(UUID id);
void deleteById(UUID id);
}

View File

@@ -1,18 +0,0 @@
package com.rviewer.skeletons.infrastructure.persistence;
import com.rviewer.skeletons.domain.services.persistence.DatabaseConnector;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
@Component
public class PostgresConnector implements DatabaseConnector {
@Autowired
JdbcTemplate jdbcTemplate;
public int getConnectionStatus() {
return jdbcTemplate.queryForObject("SELECT 1+1", Integer.class);
}
}

View File

@@ -1,9 +1,11 @@
server.port=8080
application.title=Java skeleton
application.title=Cartfidential
application.version=1.0.0
spring.datasource.initialization-mode=always
spring.datasource.username = rv_user
spring.datasource.password = rv_password
spring.datasource.driverClassName = org.postgresql.Driver
spring.datasource.url = jdbc:postgresql://postgres-skeleton-db:5432/postgres_rv_database
spring.jpa.hibernate.ddl-auto = update
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.datasource.url = jdbc:postgresql://localhost:5432/postgres_rv_database

View File

@@ -1,34 +0,0 @@
package com.rviewer.skeletons.infrastructure.controllers;
import org.junit.jupiter.api.Test;
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.web.servlet.MockMvc;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
public class PingControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void getPing_success() throws Exception {
mockMvc
.perform(get("/ping"))
.andExpect(status().isOk());
}
@Test
public void getPing_returnsPong() throws Exception {
mockMvc
.perform(get("/ping"))
.andExpect(content().string(containsString("pong")));
}
}

View File

@@ -0,0 +1,217 @@
package com.rviewer.skeletons.infrastructure.inbound.api.adapter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.rviewer.skeletons.domain.model.Cart;
import com.rviewer.skeletons.domain.model.Item;
import com.rviewer.skeletons.domain.service.CartService;
import com.rviewer.skeletons.infrastructure.inbound.api.exception.CartAlreadyExistsException;
import com.rviewer.skeletons.infrastructure.inbound.api.exception.CartNotFoundException;
import org.junit.jupiter.api.Test;
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.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.web.bind.MethodArgumentNotValidException;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import static com.rviewer.skeletons.infrastructure.inbound.api.exception.ControllerExceptionHandler.INVALID_BODY;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
class CartControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private CartService cartService;
@Test
void should_return_cart() throws Exception {
UUID id = UUID.randomUUID();
Item item = new Item(UUID.randomUUID(), "item", 1, 100F);
Cart expectedCart = new Cart(id, List.of(item));
when(cartService.get(id.toString())).thenReturn(Optional.of(expectedCart));
mockMvc.perform(MockMvcRequestBuilders.get("/carts/" + id))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(id.toString()))
.andExpect(jsonPath("$.items[0].id").value(item.getId().toString()))
.andExpect(jsonPath("$.items[0].name").value(item.getName()))
.andExpect(jsonPath("$.items[0].quantity").value(item.getQuantity()))
.andExpect(jsonPath("$.items[0].price").value(item.getPrice()));
}
@Test
void should_not_return_non_existing_cart() throws Exception {
UUID id = UUID.randomUUID();
when(cartService.get(id.toString())).thenReturn(Optional.empty());
mockMvc.perform(MockMvcRequestBuilders.get("/carts/" + id))
.andExpect(status().isNotFound())
.andExpect(result -> assertTrue(result.getResolvedException() instanceof CartNotFoundException));
}
@Test
void should_save_new_cart() throws Exception {
UUID id = UUID.randomUUID();
Item item = new Item(UUID.randomUUID(), "item", 1, 100F);
Cart newCart = new Cart(id, List.of(item));
mockMvc.perform(MockMvcRequestBuilders.post("/carts/" + id)
.content(asJsonString(newCart))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isCreated());
}
@Test
void should_not_save_old_cart() throws Exception {
UUID id = UUID.randomUUID();
Item item = new Item(UUID.randomUUID(), "item", 1, 100F);
Cart oldCart = new Cart(id, List.of(item));
when(cartService.get(id.toString())).thenReturn(Optional.of(oldCart));
mockMvc.perform(MockMvcRequestBuilders.post("/carts/" + id)
.content(asJsonString(oldCart))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isConflict())
.andExpect(result -> assertTrue(result.getResolvedException() instanceof CartAlreadyExistsException))
;
}
@Test
void should_not_save_null_cart() throws Exception {
UUID id = UUID.randomUUID();
Cart nullCart = new Cart(id, null);
mockMvc.perform(MockMvcRequestBuilders.post("/carts/" + id)
.content(asJsonString(nullCart))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest())
.andExpect(result -> assertTrue(result.getResolvedException() instanceof MethodArgumentNotValidException))
.andExpect(result -> assertEquals(INVALID_BODY, result.getResponse().getContentAsString()));
}
@Test
void should_not_save_bad_cart() throws Exception {
UUID id = UUID.randomUUID();
Item item = new Item(UUID.randomUUID(), "item", 1, 100F);
Cart badCart = new Cart(id, List.of(item));
mockMvc.perform(MockMvcRequestBuilders.post("/carts/" + id)
.content(asJsonString(badCart).replace("100.0", ""))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest())
.andExpect(result -> assertTrue(result.getResolvedException() instanceof HttpMessageNotReadableException))
.andExpect(result -> assertEquals(INVALID_BODY, result.getResponse().getContentAsString()));
}
@Test
void should_update_old_cart() throws Exception {
UUID id = UUID.randomUUID();
Item item = new Item(UUID.randomUUID(), "item", 1, 100F);
Cart oldCart = new Cart(id, List.of(item));
when(cartService.get(id.toString())).thenReturn(Optional.of(oldCart));
mockMvc.perform(MockMvcRequestBuilders.patch("/carts/" + id)
.content(asJsonString(oldCart).replace("100.0", "1000.0"))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(result -> assertEquals(1000F, cartService.get(id.toString()).orElseThrow().getItems().get(0).getPrice()));
}
@Test
void should_not_update_non_existing_cart() throws Exception {
UUID id = UUID.randomUUID();
Item item = new Item(UUID.randomUUID(), "item", 1, 100F);
Cart nonExistingCart = new Cart(id, List.of(item));
when(cartService.get(id.toString())).thenReturn(Optional.empty());
mockMvc.perform(MockMvcRequestBuilders.patch("/carts/" + id)
.content(asJsonString(nonExistingCart))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound())
.andExpect(result -> assertTrue(result.getResolvedException() instanceof CartNotFoundException));
}
@Test
void should_not_update_null_cart() throws Exception {
UUID id = UUID.randomUUID();
Cart nullCart = new Cart(id, null);
mockMvc.perform(MockMvcRequestBuilders.patch("/carts/" + id)
.content(asJsonString(nullCart))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest())
.andExpect(result -> assertTrue(result.getResolvedException() instanceof MethodArgumentNotValidException))
.andExpect(result -> assertEquals(INVALID_BODY, result.getResponse().getContentAsString()));
}
@Test
void should_not_update_bad_cart() throws Exception {
UUID id = UUID.randomUUID();
Item item = new Item(UUID.randomUUID(), "item", 1, 100F);
Cart badCart = new Cart(id, List.of(item));
mockMvc.perform(MockMvcRequestBuilders.patch("/carts/" + id)
.content(asJsonString(badCart).replace("100.0", ""))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest())
.andExpect(result -> assertTrue(result.getResolvedException() instanceof HttpMessageNotReadableException))
.andExpect(result -> assertEquals(INVALID_BODY, result.getResponse().getContentAsString()))
;
}
@Test
void should_delete_old_cart() throws Exception {
UUID id = UUID.randomUUID();
Item item = new Item(UUID.randomUUID(), "item", 1, 100F);
Cart oldCart = new Cart(id, List.of(item));
when(cartService.get(id.toString())).thenReturn(Optional.of(oldCart));
mockMvc.perform(MockMvcRequestBuilders.delete("/carts/" + id)
.content(asJsonString(oldCart))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
@Test
void should_not_delete_non_existing_cart() throws Exception {
UUID id = UUID.randomUUID();
Item item = new Item(UUID.randomUUID(), "item", 1, 100F);
Cart nonExistingCart = new Cart(id, List.of(item));
when(cartService.get(id.toString())).thenReturn(Optional.empty());
mockMvc.perform(MockMvcRequestBuilders.delete("/carts/" + id)
.content(asJsonString(nonExistingCart))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound())
.andExpect(result -> assertTrue(result.getResolvedException() instanceof CartNotFoundException));
}
private static String asJsonString(final Object obj) {
try {
return new ObjectMapper().writeValueAsString(obj);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,75 @@
package com.rviewer.skeletons.infrastructure.outbound.adapter;
import com.rviewer.skeletons.domain.model.Cart;
import com.rviewer.skeletons.domain.model.Item;
import com.rviewer.skeletons.infrastructure.outbound.database.adapter.PostgresAdapter;
import com.rviewer.skeletons.infrastructure.outbound.database.dto.PostgresCart;
import com.rviewer.skeletons.infrastructure.outbound.database.repository.PostgresCartRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@SpringBootTest
class PostgresAdapterTest {
@Autowired
private PostgresAdapter postgresAdapter;
@Mock
private PostgresCartRepository postgresCartRepository;
@BeforeEach
void init() {
postgresAdapter = new PostgresAdapter(postgresCartRepository);
}
@Test
void should_get_cart() {
UUID id = UUID.randomUUID();
Item item = new Item(UUID.randomUUID(), "item", 1, 100F);
Cart expectedCart = new Cart(id, List.of(item));
when(postgresCartRepository.findById(id)).thenReturn(Optional.ofNullable(PostgresCart.fromDomain(expectedCart)));
Optional<Cart> result = postgresAdapter.get(id.toString());
assertThat(Optional.of(result)).hasValue(Optional.of(expectedCart));
}
@Test
void should_save_cart() {
UUID id = UUID.randomUUID();
Item item = new Item(UUID.randomUUID(), "item", 1, 100F);
Cart newCart = new Cart(id, List.of(item));
postgresAdapter.save(newCart);
ArgumentCaptor<PostgresCart> captor = ArgumentCaptor.forClass(PostgresCart.class);
verify(postgresCartRepository).save(captor.capture());
assertEquals(captor.getValue().toDomain(), newCart);
}
@Test
void should_delete_cart() {
UUID id = UUID.randomUUID();
Item item = new Item(UUID.randomUUID(), "item", 1, 100F);
Cart cartToDelete = new Cart(id, List.of(item));
when(postgresCartRepository.findById(id)).thenReturn(Optional.ofNullable(PostgresCart.fromDomain(cartToDelete)));
postgresAdapter.delete(cartToDelete.getId().toString());
verify(postgresCartRepository, times(1)).deleteById(cartToDelete.getId());
}
}

View File

@@ -1,25 +0,0 @@
package com.rviewer.skeletons.infrastructure.persistence;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
@SpringBootTest
public class PostgresConnectorTest {
@Mock
private JdbcTemplate jdbcTemplate;
@Autowired
private PostgresConnector postgresConnector;
@Test
public void getConnectionStatus_shouldReturn2() {
when(jdbcTemplate.queryForObject("SELECT 1+1", Integer.class)).thenReturn(2);
assertEquals(2, postgresConnector.getConnectionStatus());
}
}

View File

@@ -1,9 +1,9 @@
server.port=8080
application.title=Java skeleton
application.title=Cartfidential
application.version=1.0.0
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.username=user
spring.datasource.password=password
spring.datasource.password=password