Compare commits

...

3 Commits

Author SHA1 Message Date
Meutel deb36f05e3 Error handling, GET receipe 2023-10-23 19:00:33 +02:00
Meutel f5efa35c3d Manual tests bruno 2023-10-23 18:59:39 +02:00
Meutel 6ff35ad6cf suppr module HATEOAS 2023-10-23 18:11:17 +02:00
13 changed files with 157 additions and 45 deletions

View File

@ -31,10 +31,6 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
@ -130,7 +126,6 @@
<useSpringBoot3>true</useSpringBoot3>
<apiPackage>net.meutel.recettes.api</apiPackage>
<modelPackage>net.meutel.recettes.api.model</modelPackage>
<hateoas>true</hateoas>
<performBeanValidation>true</performBeanValidation>
<useOptional>true</useOptional>
<useSwaggerUI>false</useSwaggerUI>

View File

@ -1,19 +1,16 @@
package net.meutel.recettes.api.controller;
import java.util.List;
import java.util.Optional;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.NativeWebRequest;
import net.meutel.recettes.api.ReceipeApi;
import net.meutel.recettes.api.ReceipesApi;
import net.meutel.recettes.api.model.Receipe;
import net.meutel.recettes.api.service.ReceipeService;
@RestController
public class ReceipeController implements ReceipeApi, ReceipesApi {
public class ReceipeController implements ReceipesApi {
private final ReceipeService service;
@ -21,14 +18,14 @@ public class ReceipeController implements ReceipeApi, ReceipesApi {
this.service = service;
}
@Override
public Optional<NativeWebRequest> getRequest() {
return ReceipeApi.super.getRequest();
}
@Override
public ResponseEntity<List<Receipe>> findReceipes() {
return ResponseEntity.ok(service.findReceipes());
}
@Override
public ResponseEntity<Receipe> getReceipeById(String receipeId) {
return ResponseEntity.ok(service.getReceipeById(receipeId));
}
}

View File

@ -0,0 +1,17 @@
package net.meutel.recettes.api.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(ItemNotFoundException.class)
ProblemDetail handleBookmarkNotFoundException(ItemNotFoundException e) {
return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, e.getMessage());
}
}

View File

@ -0,0 +1,9 @@
package net.meutel.recettes.api.exception;
public class ItemNotFoundException extends RuntimeException {
public ItemNotFoundException(String itemName, Object id) {
super(itemName + " not found with id " + id);
}
}

View File

@ -6,7 +6,7 @@ import net.meutel.recettes.api.entity.ReceipeEntity;
import net.meutel.recettes.api.model.Receipe;
@Mapper(componentModel = "spring")
public interface ReceipeMapper {
public interface ReceipeFullMapper {
Receipe fromEntity(ReceipeEntity entity);

View File

@ -0,0 +1,17 @@
package net.meutel.recettes.api.mapper;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import net.meutel.recettes.api.entity.ReceipeEntity;
import net.meutel.recettes.api.model.Receipe;
@Mapper(componentModel = "spring")
public interface ReceipeSimplifiedMapper {
@Mapping(target = "receipeYield", ignore = true)
Receipe fromEntity(ReceipeEntity entity);
ReceipeEntity toEntity(Receipe model);
}

View File

@ -2,6 +2,9 @@ package net.meutel.recettes.api.service;
import java.util.List;
import org.springframework.lang.NonNull;
import net.meutel.recettes.api.exception.ItemNotFoundException;
import net.meutel.recettes.api.model.Receipe;
public interface ReceipeService {
@ -10,4 +13,9 @@ public interface ReceipeService {
return List.of();
}
@NonNull
default Receipe getReceipeById(String id) {
throw new ItemNotFoundException("Receipe", id);
}
}

View File

@ -5,7 +5,8 @@ import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
import net.meutel.recettes.api.mapper.ReceipeMapper;
import net.meutel.recettes.api.exception.ItemNotFoundException;
import net.meutel.recettes.api.mapper.ReceipeSimplifiedMapper;
import net.meutel.recettes.api.model.Receipe;
import net.meutel.recettes.api.repository.ReceipeRepository;
@ -13,9 +14,9 @@ import net.meutel.recettes.api.repository.ReceipeRepository;
public class ReceipeServiceImpl implements ReceipeService {
private final ReceipeRepository repo;
private final ReceipeMapper mapper;
private final ReceipeSimplifiedMapper mapper;
public ReceipeServiceImpl(ReceipeRepository repo, ReceipeMapper mapper) {
public ReceipeServiceImpl(ReceipeRepository repo, ReceipeSimplifiedMapper mapper) {
this.repo = repo;
this.mapper = mapper;
}
@ -28,4 +29,11 @@ public class ReceipeServiceImpl implements ReceipeService {
.collect(Collectors.toList());
}
@Override
public Receipe getReceipeById(String id) {
return repo.findById(id)
.map(mapper::fromEntity)
.orElseThrow(() -> new ItemNotFoundException("Receipe", id));
}
}

View File

@ -16,42 +16,64 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
import net.meutel.recettes.api.exception.ItemNotFoundException;
import net.meutel.recettes.api.model.Receipe;
import net.meutel.recettes.api.service.ReceipeService;
@SpringBootTest
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class ReceipeControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private MockMvc mockMvc;
@MockBean
private ReceipeService service;
@MockBean
private ReceipeService service;
@Test
public void findReceipes_none_empty() throws Exception {
this.mockMvc.perform(get("/receipes"))
.andExpect(status().isOk())
.andExpect(jsonPath("$").isArray())
.andExpect(jsonPath("$").isEmpty());
public void getReceipeById_ItemNotFound_404() throws Exception {
when(service.getReceipeById("TEST"))
.thenThrow(new ItemNotFoundException("Receipe", "TEST"));
this.mockMvc.perform(get("/receipes/TEST"))
.andExpect(status().isNotFound());
}
@Test
public void findReceipes_exists_list() throws Exception {
Receipe r1 = new Receipe()
.name("pates carbo");
Receipe r2 = new Receipe()
.name("soupe a l'oignon");
when(service.findReceipes())
.thenReturn(List.of(r1, r2));
@Test
public void getReceipeById_exixts_success() throws Exception {
Receipe r1 = new Receipe()
.name("pates carbo");
when(service.getReceipeById("TEST"))
.thenReturn(r1);
this.mockMvc.perform(get("/receipes"))
.andExpect(status().isOk())
.andExpect(jsonPath("$").isArray())
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$.[*].name").exists())
.andExpect(jsonPath("$.[0].name", equalTo("pates carbo")))
.andExpect(jsonPath("$.[*].description").doesNotExist());
}
this.mockMvc.perform(get("/receipes/TEST"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name", equalTo("pates carbo")));
}
@Test
public void findReceipes_none_empty() throws Exception {
this.mockMvc.perform(get("/receipes"))
.andExpect(status().isOk())
.andExpect(jsonPath("$").isArray())
.andExpect(jsonPath("$").isEmpty());
}
@Test
public void findReceipes_exists_list() throws Exception {
Receipe r1 = new Receipe()
.name("pates carbo");
Receipe r2 = new Receipe()
.name("soupe a l'oignon");
when(service.findReceipes())
.thenReturn(List.of(r1, r2));
this.mockMvc.perform(get("/receipes"))
.andExpect(status().isOk())
.andExpect(jsonPath("$").isArray())
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$.[*].name").exists())
.andExpect(jsonPath("$.[0].name", equalTo("pates carbo")))
.andExpect(jsonPath("$.[*].description").doesNotExist());
}
}

View File

@ -4,13 +4,16 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import net.meutel.recettes.api.entity.ReceipeEntity;
import net.meutel.recettes.api.exception.ItemNotFoundException;
import net.meutel.recettes.api.model.Receipe;
import net.meutel.recettes.api.repository.ReceipeRepository;
@ -23,6 +26,26 @@ public class ReceipeServiceImplTest {
@MockBean
ReceipeRepository repo;
@Test
void getReceipeById_noData_exception() {
Assertions.assertThrows(ItemNotFoundException.class, () -> tested.getReceipeById("TEST"));
}
@Test
void getReceipeById_exists_receipe() {
ReceipeEntity r1 = new ReceipeEntity();
r1.setName("pates carbo");
when(repo.findById("TEST"))
.thenReturn(Optional.of(r1));
var result = tested.getReceipeById("TEST");
assertThat(result)
.isNotNull()
.extracting(Receipe::getName)
.isEqualTo("pates carbo");
}
@Test
void findReceipes_noData_empty() {
var result = tested.findReceipes();

View File

@ -0,0 +1,11 @@
meta {
name: GET receipe notfound
type: http
seq: 1
}
get {
url: http://localhost:8080/receipes/test
body: none
auth: none
}

5
doc/api/bruno/bruno.json Normal file
View File

@ -0,0 +1,5 @@
{
"version": "1",
"name": "Recettes",
"type": "collection"
}

View File

@ -34,7 +34,7 @@ paths:
items:
$ref: '#/components/schemas/Receipe'
/receipe/{receipeId}:
/receipes/{receipeId}:
get:
tags:
- receipe