From 4c7b18cfb6865eac48c9ce7292d2afb3424c1ae4 Mon Sep 17 00:00:00 2001 From: Nataya Price Date: Tue, 9 Apr 2024 02:07:29 +0000 Subject: [PATCH 1/4] feat: Revised MediaController --- .../lesson16/web/MediaItemsController.java | 27 ++++++ .../lesson16/web/PatronsController.java | 56 +++++++++++ .../lesson16/web/PatronsControllerTest.java | 97 +++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsController.java create mode 100644 lesson_16/api/api_app/src/test/java/com/codedifferently/lesson16/web/PatronsControllerTest.java diff --git a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/MediaItemsController.java b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/MediaItemsController.java index cae5ff06b..51f0f41cb 100644 --- a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/MediaItemsController.java +++ b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/MediaItemsController.java @@ -7,7 +7,13 @@ import java.io.IOException; import java.util.List; import java.util.Set; +import java.util.UUID; +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.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController @@ -27,4 +33,25 @@ public GetMediaItemsResponse getItems() { var response = GetMediaItemsResponse.builder().items(responseItems).build(); return response; } + + @PostMapping("/items") + public CreateMediaItemResponse createItem(@RequestBody CreateMediaItemRequest request) { + MediaItem item = MediaItemRequest.asMediaItem(request.getItem()); + library.addMediaItem(item, librarian); + return CreateMediaItemResponse.builder().items(responseItems).build(); + } + + @GetMapping("/items/{id}") + public GetMediaItemsResponse getItem() { + Set items = library.search(SearchCriteria.builder() @PathVariable("id").build()); + List responseItems = items.stream().map(MediaItemResponse::from).toList(); + var response = GetMediaItemsResponse.builder().items(responseItems).build(); + return response; + } + + @DeleteMapping("/items/{id}") + public ResponseEntity deleteItem(@PathVariable("id") UUID id) { + library.removeMediaItem(id, librarian); + return ResponseEntity.noContent().build(); + } } diff --git a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsController.java b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsController.java new file mode 100644 index 000000000..a9fef6294 --- /dev/null +++ b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsController.java @@ -0,0 +1,56 @@ +package com.codedifferently.lesson16.web; + + import com.codedifferently.lesson16.library.Librarian; + import com.codedifferently.lesson16.library.Library; + import com.codedifferently.lesson16.library.Patron; + import com.codedifferently.lesson16.library.search.PatronSearchCriteria; + import java.io.IOException; + import java.util.List; + import java.util.Set; + import org.springframework.http.HttpStatus; + import org.springframework.http.ResponseEntity; + import org.springframework.web.bind.annotation.*; + + @RestController + public class PatronsController { + private final Library library; + private final Librarian librarian; + + public PatronsController(Library library) throws IOException { + this.library = library; + this.librarian = library.getLibrarians().stream().findFirst().orElseThrow(); + } + + @GetMapping("/patrons") + public ResponseEntity getPatrons() { + Set patrons = library.searchPatrons(PatronSearchCriteria.builder().build()); + List responsePatrons = PatronResponse.from(patrons); + return ResponseEntity.ok(GetPatronsResponse.builder().patrons(responsePatrons).build()); + } + + @PostMapping("/patrons") + public ResponseEntity addPatron(@RequestBody PatronRequest patronRequest) { + Patron patron = PatronRequest.toPatron(patronRequest); + library.add(patron, librarian); + return ResponseEntity.status(HttpStatus.CREATED).body(AddPatronResponse.from(patron)); + } + + @GetMapping("/patrons/{id}") + public ResponseEntity getPatron(@PathVariable String id) { + Patron patron = library.findPatronById(id); + if (patron == null) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(PatronResponse.from(patron)); + } + + @DeleteMapping("/patrons/{id}") + public ResponseEntity deletePatron(@PathVariable String id) { + Patron patron = library.findPatronById(id); + if (patron == null) { + return ResponseEntity.notFound().build(); + } + library.remove(patron, librarian); + return ResponseEntity.noContent().build(); + } + } diff --git a/lesson_16/api/api_app/src/test/java/com/codedifferently/lesson16/web/PatronsControllerTest.java b/lesson_16/api/api_app/src/test/java/com/codedifferently/lesson16/web/PatronsControllerTest.java new file mode 100644 index 000000000..814e0fd6b --- /dev/null +++ b/lesson_16/api/api_app/src/test/java/com/codedifferently/lesson16/web/PatronsControllerTest.java @@ -0,0 +1,97 @@ +package com.codedifferently.lesson16.web; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.codedifferently.lesson16.Lesson16; +import com.codedifferently.lesson16.library.Library; +import com.codedifferently.lesson16.library.Patron; +import com.codedifferently.lesson16.library.search.PatronSearchCriteria; +import java.util.Set; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@SpringBootTest +@ContextConfiguration(classes = Lesson16.class) +class PatronsControllerTest { + private static MockMvc mockMvc; + @Autowired private Library library; + + @BeforeAll + static void setUp(WebApplicationContext wac) { + mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); + } + + @Test + void testController_getsAllPatrons() throws Exception { + mockMvc + .perform(get("/patrons").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.patrons").isArray()); + } + + @Test + void testController_getsAPatron() throws Exception { + mockMvc + .perform(get("/patrons/12345").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } + + @Test + void testController_returnsNotFoundOnGetPatron() throws Exception { + mockMvc + .perform(get("/patrons/00000").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + void testController_reportsBadRequestOnAddPatron() throws Exception { + String json = "{}"; + + mockMvc + .perform(post("/patrons").contentType(MediaType.APPLICATION_JSON).content(json)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.errors").isArray()) + .andExpect(jsonPath("$.errors.length()").value(1)); + } + + @Test + void testController_addsPatron() throws Exception { + String json = "{\"name\": \"John Doe\"}"; + + mockMvc + .perform(post("/patrons").contentType(MediaType.APPLICATION_JSON).content(json)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.patron.id").isString()); + + Set patrons = + library.searchPatrons(PatronSearchCriteria.builder().name("John Doe").build()); + assertThat(patrons).hasSize(1); + } + + @Test + void testController_returnsNotFoundOnDeletePatron() throws Exception { + mockMvc + .perform(delete("/patrons/00000").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + void testController_deletesPatron() throws Exception { + mockMvc + .perform(delete("/patrons/12345").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()); + + Set patrons = library.searchPatrons(PatronSearchCriteria.builder().id("12345").build()); + assertThat(patrons).hasSize(0); + } +} From afd17e0e04be74f64aa76dbc70b8e015bf7defe0 Mon Sep 17 00:00:00 2001 From: Nataya Price Date: Wed, 10 Apr 2024 15:24:34 +0000 Subject: [PATCH 2/4] feat: Created new files to accomodate PatronsController --- .../library/search/PatronSearchCriteria.java | 12 ++ .../lesson16/web/CreatePatronRequest.java | 18 ++ .../lesson16/web/CreatePatronResponse.java | 11 ++ .../lesson16/web/GetPatronResponse.java | 14 ++ .../lesson16/web/MediaItemsController.java | 16 +- .../lesson16/web/PatronRequest.java | 5 + .../lesson16/web/PatronResponse.java | 5 + .../lesson16/web/PatronsController.java | 111 ++++++------ .../lesson16/web/PatronsControllerTest.java | 163 ++++++++++-------- 9 files changed, 220 insertions(+), 135 deletions(-) create mode 100644 lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/library/search/PatronSearchCriteria.java create mode 100644 lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/CreatePatronRequest.java create mode 100644 lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/CreatePatronResponse.java create mode 100644 lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/GetPatronResponse.java create mode 100644 lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronRequest.java create mode 100644 lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronResponse.java diff --git a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/library/search/PatronSearchCriteria.java b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/library/search/PatronSearchCriteria.java new file mode 100644 index 000000000..5a92cc35d --- /dev/null +++ b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/library/search/PatronSearchCriteria.java @@ -0,0 +1,12 @@ +package com.codedifferently.lesson16.library.search; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +public class PatronSearchCriteria { + public String name; + public String email; + public String id; +} diff --git a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/CreatePatronRequest.java b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/CreatePatronRequest.java new file mode 100644 index 000000000..4cf7cb540 --- /dev/null +++ b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/CreatePatronRequest.java @@ -0,0 +1,18 @@ +package com.codedifferently.lesson16.web; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class CreatePatronRequest { + @NotNull(message = "item is required") @Valid + private PatronRequest patron; + +} diff --git a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/CreatePatronResponse.java b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/CreatePatronResponse.java new file mode 100644 index 000000000..f76c806ca --- /dev/null +++ b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/CreatePatronResponse.java @@ -0,0 +1,11 @@ +package com.codedifferently.lesson16.web; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class CreatePatronResponse { +private PatronResponse patron; + +} diff --git a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/GetPatronResponse.java b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/GetPatronResponse.java new file mode 100644 index 000000000..0abf0c1cc --- /dev/null +++ b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/GetPatronResponse.java @@ -0,0 +1,14 @@ +package com.codedifferently.lesson16.web; + +import java.util.List; +import lombok.Builder; +import lombok.Data; +import lombok.Singular; + +@Data +@Builder +public class GetPatronResponse { + @Singular private List patronResponses; +} + + diff --git a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/MediaItemsController.java b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/MediaItemsController.java index 51f0f41cb..a8bbcc6a5 100644 --- a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/MediaItemsController.java +++ b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/MediaItemsController.java @@ -6,6 +6,7 @@ import com.codedifferently.lesson16.library.search.SearchCriteria; import java.io.IOException; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.UUID; import org.springframework.http.ResponseEntity; @@ -38,15 +39,18 @@ public GetMediaItemsResponse getItems() { public CreateMediaItemResponse createItem(@RequestBody CreateMediaItemRequest request) { MediaItem item = MediaItemRequest.asMediaItem(request.getItem()); library.addMediaItem(item, librarian); - return CreateMediaItemResponse.builder().items(responseItems).build(); + return CreateMediaItemResponse.builder().item(MediaItemResponse.from(item)).build(); } @GetMapping("/items/{id}") - public GetMediaItemsResponse getItem() { - Set items = library.search(SearchCriteria.builder() @PathVariable("id").build()); - List responseItems = items.stream().map(MediaItemResponse::from).toList(); - var response = GetMediaItemsResponse.builder().items(responseItems).build(); - return response; + public ResponseEntity getItem(@PathVariable UUID id) { + Optional item = + library.search(SearchCriteria.builder().id(id.toString()).build()).stream().findFirst(); + + if (item.isEmpty()) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(MediaItemResponse.from(item.get())); } @DeleteMapping("/items/{id}") diff --git a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronRequest.java b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronRequest.java new file mode 100644 index 000000000..0aac28b69 --- /dev/null +++ b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronRequest.java @@ -0,0 +1,5 @@ +package com.codedifferently.lesson16.web; + +public class PatronRequest { + +} diff --git a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronResponse.java b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronResponse.java new file mode 100644 index 000000000..27498d5e0 --- /dev/null +++ b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronResponse.java @@ -0,0 +1,5 @@ +package com.codedifferently.lesson16.web; + +public class PatronResponse { + +} diff --git a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsController.java b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsController.java index a9fef6294..6cf27abe4 100644 --- a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsController.java +++ b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsController.java @@ -1,56 +1,59 @@ package com.codedifferently.lesson16.web; - import com.codedifferently.lesson16.library.Librarian; - import com.codedifferently.lesson16.library.Library; - import com.codedifferently.lesson16.library.Patron; - import com.codedifferently.lesson16.library.search.PatronSearchCriteria; - import java.io.IOException; - import java.util.List; - import java.util.Set; - import org.springframework.http.HttpStatus; - import org.springframework.http.ResponseEntity; - import org.springframework.web.bind.annotation.*; - - @RestController - public class PatronsController { - private final Library library; - private final Librarian librarian; - - public PatronsController(Library library) throws IOException { - this.library = library; - this.librarian = library.getLibrarians().stream().findFirst().orElseThrow(); - } - - @GetMapping("/patrons") - public ResponseEntity getPatrons() { - Set patrons = library.searchPatrons(PatronSearchCriteria.builder().build()); - List responsePatrons = PatronResponse.from(patrons); - return ResponseEntity.ok(GetPatronsResponse.builder().patrons(responsePatrons).build()); - } - - @PostMapping("/patrons") - public ResponseEntity addPatron(@RequestBody PatronRequest patronRequest) { - Patron patron = PatronRequest.toPatron(patronRequest); - library.add(patron, librarian); - return ResponseEntity.status(HttpStatus.CREATED).body(AddPatronResponse.from(patron)); - } - - @GetMapping("/patrons/{id}") - public ResponseEntity getPatron(@PathVariable String id) { - Patron patron = library.findPatronById(id); - if (patron == null) { - return ResponseEntity.notFound().build(); - } - return ResponseEntity.ok(PatronResponse.from(patron)); - } - - @DeleteMapping("/patrons/{id}") - public ResponseEntity deletePatron(@PathVariable String id) { - Patron patron = library.findPatronById(id); - if (patron == null) { - return ResponseEntity.notFound().build(); - } - library.remove(patron, librarian); - return ResponseEntity.noContent().build(); - } - } +import java.io.IOException; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import com.codedifferently.lesson16.library.Library; +import com.codedifferently.lesson16.library.Librarian; +import com.codedifferently.lesson16.library.Patron; +import com.codedifferently.lesson16.library.search.PatronSearchCriteria; + +@RestController +public class PatronsController { + private final Library library; + private final Librarian librarian; + + public PatronsController(Library library) throws IOException { + this.library = library; + this.librarian = library.getLibrarians().stream().findFirst().orElseThrow(); + } + + @GetMapping("/patrons") + public ResponseEntity getPatrons() { + Set patrons = library.searchPatrons(PatronSearchCriteria.builder().build()); + List responsePatrons = PatronResponse.from(patrons); + return ResponseEntity.ok(GetPatronResponse.builder().patrons(responsePatrons).build()); + } + + @PostMapping("/patrons") + public ResponseEntity addPatron(@RequestBody PatronRequest patronRequest) { + Patron patron = PatronRequest.toPatron(patronRequest); + library.addLibraryGuest(patron); + return ResponseEntity.status(HttpStatus.CREATED).body(GetPatronResponse.from(patron)); + } + + @GetMapping("/patrons/{id}") + public ResponseEntity getPatron(@PathVariable UUID id) { + Patron patron = library.getPatrons(id); + if (patron == null) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(PatronResponse.from(patron)); + } + + @DeleteMapping("/patrons/{id}") + public ResponseEntity deletePatron(@PathVariable UUID id) { + Patron patron = library.getPatrons(id); + if (patron == null) { + return ResponseEntity.notFound().build(); + } + library.removeLibraryGuest(patron); + return ResponseEntity.noContent().build(); + } +} diff --git a/lesson_16/api/api_app/src/test/java/com/codedifferently/lesson16/web/PatronsControllerTest.java b/lesson_16/api/api_app/src/test/java/com/codedifferently/lesson16/web/PatronsControllerTest.java index 814e0fd6b..862815beb 100644 --- a/lesson_16/api/api_app/src/test/java/com/codedifferently/lesson16/web/PatronsControllerTest.java +++ b/lesson_16/api/api_app/src/test/java/com/codedifferently/lesson16/web/PatronsControllerTest.java @@ -5,14 +5,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import com.codedifferently.lesson16.Lesson16; import com.codedifferently.lesson16.library.Library; import com.codedifferently.lesson16.library.Patron; import com.codedifferently.lesson16.library.search.PatronSearchCriteria; -import java.util.Set; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; 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.http.MediaType; import org.springframework.test.context.ContextConfiguration; @@ -20,78 +19,92 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; +import java.util.Set; + @SpringBootTest -@ContextConfiguration(classes = Lesson16.class) +@AutoConfigureMockMvc +@ContextConfiguration(classes = {Library.class, PatronsController.class}) class PatronsControllerTest { - private static MockMvc mockMvc; - @Autowired private Library library; - - @BeforeAll - static void setUp(WebApplicationContext wac) { - mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); - } - - @Test - void testController_getsAllPatrons() throws Exception { - mockMvc - .perform(get("/patrons").contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.patrons").isArray()); - } - - @Test - void testController_getsAPatron() throws Exception { - mockMvc - .perform(get("/patrons/12345").contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()); - } - - @Test - void testController_returnsNotFoundOnGetPatron() throws Exception { - mockMvc - .perform(get("/patrons/00000").contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotFound()); - } - - @Test - void testController_reportsBadRequestOnAddPatron() throws Exception { - String json = "{}"; - - mockMvc - .perform(post("/patrons").contentType(MediaType.APPLICATION_JSON).content(json)) - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.errors").isArray()) - .andExpect(jsonPath("$.errors.length()").value(1)); - } - - @Test - void testController_addsPatron() throws Exception { - String json = "{\"name\": \"John Doe\"}"; - - mockMvc - .perform(post("/patrons").contentType(MediaType.APPLICATION_JSON).content(json)) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$.patron.id").isString()); - - Set patrons = - library.searchPatrons(PatronSearchCriteria.builder().name("John Doe").build()); - assertThat(patrons).hasSize(1); - } - - @Test - void testController_returnsNotFoundOnDeletePatron() throws Exception { - mockMvc - .perform(delete("/patrons/00000").contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotFound()); - } - - @Test - void testController_deletesPatron() throws Exception { - mockMvc - .perform(delete("/patrons/12345").contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isNoContent()); - - Set patrons = library.searchPatrons(PatronSearchCriteria.builder().id("12345").build()); - assertThat(patrons).hasSize(0); - } + + @Autowired + private MockMvc mockMvc; + + @Autowired + private Library library; + + @BeforeEach + void setUp() { + library.clearPatrons(); // Clear patrons before each test + } + + @Test + void testController_getsAllPatrons() throws Exception { + mockMvc.perform(get("/patrons") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.patrons").isArray()); + } + + @Test + void testController_getsAPatron() throws Exception { + Patron patron = new Patron("John Doe"); + library.addLibraryGuest(patron); + + mockMvc.perform(get("/patrons/{id}", patron.getId()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } + + @Test + void testController_returnsNotFoundOnGetPatron() throws Exception { + mockMvc.perform(get("/patrons/{id}", "00000") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + void testController_reportsBadRequestOnAddPatron() throws Exception { + String json = "{}"; + + mockMvc.perform(post("/patrons") + .contentType(MediaType.APPLICATION_JSON) + .content(json)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.errors").isArray()) + .andExpect(jsonPath("$.errors.length()").value(1)); + } + + @Test + void testController_addsPatron() throws Exception { + String json = "{\"name\": \"John Doe\"}"; + + mockMvc.perform(post("/patrons") + .contentType(MediaType.APPLICATION_JSON) + .content(json)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.patron.id").isString()); + + Set patrons = library.searchPatrons(PatronSearchCriteria.builder().name("John Doe").build()); + assertThat(patrons).hasSize(1); + } + + @Test + void testController_returnsNotFoundOnDeletePatron() throws Exception { + mockMvc.perform(delete("/patrons/{id}", "00000") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + void testController_deletesPatron() throws Exception { + Patron patron = new Patron("John Doe"); + library.addLibraryGuest(patron); + + mockMvc.perform(delete("/patrons/{id}", patron.getId()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()); + + Set patrons = library.searchPatrons(PatronSearchCriteria.builder().id(patron.getId().toString()).build()); + assertThat(patrons).hasSize(0); + } } From a11c7e72b9d639d22346656c9a8861c3cb06881d Mon Sep 17 00:00:00 2001 From: Nataya Price Date: Wed, 10 Apr 2024 15:42:43 +0000 Subject: [PATCH 3/4] fix: Removed file to fix method in PatronsController --- .../library/search/PatronSearchCriteria.java | 12 --- .../lesson16/web/PatronsController.java | 81 +++++++++---------- 2 files changed, 40 insertions(+), 53 deletions(-) delete mode 100644 lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/library/search/PatronSearchCriteria.java diff --git a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/library/search/PatronSearchCriteria.java b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/library/search/PatronSearchCriteria.java deleted file mode 100644 index 5a92cc35d..000000000 --- a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/library/search/PatronSearchCriteria.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.codedifferently.lesson16.library.search; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -public class PatronSearchCriteria { - public String name; - public String email; - public String id; -} diff --git a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsController.java b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsController.java index 6cf27abe4..3b263491a 100644 --- a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsController.java +++ b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsController.java @@ -1,59 +1,58 @@ package com.codedifferently.lesson16.web; +import com.codedifferently.lesson16.library.Librarian; +import com.codedifferently.lesson16.library.Library; +import com.codedifferently.lesson16.library.Patron; +import com.codedifferently.lesson16.library.search.PatronSearchCriteria; import java.io.IOException; import java.util.List; import java.util.Set; import java.util.UUID; - import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import com.codedifferently.lesson16.library.Library; -import com.codedifferently.lesson16.library.Librarian; -import com.codedifferently.lesson16.library.Patron; -import com.codedifferently.lesson16.library.search.PatronSearchCriteria; - @RestController public class PatronsController { - private final Library library; - private final Librarian librarian; - - public PatronsController(Library library) throws IOException { - this.library = library; - this.librarian = library.getLibrarians().stream().findFirst().orElseThrow(); - } + private final Library library; + private final Librarian librarian; + + public PatronsController(Library library) throws IOException { + this.library = library; + this.librarian = library.getLibrarians().stream().findFirst().orElseThrow(); + } + + @GetMapping("/patrons") + public ResponseEntity> getPatrons() { + Set guests = library.getPatrons(); + List patrons = new ArrayList<>(guests); + return ResponseEntity.ok(patrons); +} - @GetMapping("/patrons") - public ResponseEntity getPatrons() { - Set patrons = library.searchPatrons(PatronSearchCriteria.builder().build()); - List responsePatrons = PatronResponse.from(patrons); - return ResponseEntity.ok(GetPatronResponse.builder().patrons(responsePatrons).build()); - } - @PostMapping("/patrons") - public ResponseEntity addPatron(@RequestBody PatronRequest patronRequest) { - Patron patron = PatronRequest.toPatron(patronRequest); - library.addLibraryGuest(patron); - return ResponseEntity.status(HttpStatus.CREATED).body(GetPatronResponse.from(patron)); - } + @PostMapping("/patrons") + public ResponseEntity addPatron(@RequestBody PatronRequest patronRequest) { + Patron patron = PatronRequest.toPatron(patronRequest); + library.addLibraryGuest(patron); + return ResponseEntity.status(HttpStatus.CREATED).body(GetPatronResponse.from(patron)); + } - @GetMapping("/patrons/{id}") - public ResponseEntity getPatron(@PathVariable UUID id) { - Patron patron = library.getPatrons(id); - if (patron == null) { - return ResponseEntity.notFound().build(); - } - return ResponseEntity.ok(PatronResponse.from(patron)); + @GetMapping("/patrons/{id}") + public ResponseEntity getPatron(@PathVariable UUID id) { + Patron patron = library.getPatrons(id); + if (patron == null) { + return ResponseEntity.notFound().build(); } - - @DeleteMapping("/patrons/{id}") - public ResponseEntity deletePatron(@PathVariable UUID id) { - Patron patron = library.getPatrons(id); - if (patron == null) { - return ResponseEntity.notFound().build(); - } - library.removeLibraryGuest(patron); - return ResponseEntity.noContent().build(); + return ResponseEntity.ok(PatronResponse.from(patron)); + } + + @DeleteMapping("/patrons/{id}") + public ResponseEntity deletePatron(@PathVariable UUID id) { + Patron patron = library.getPatrons(id); + if (patron == null) { + return ResponseEntity.notFound().build(); } + library.removeLibraryGuest(patron); + return ResponseEntity.noContent().build(); + } } From 7087ff48c9cb8ae0e7c0e4ff86b0b60d17011c59 Mon Sep 17 00:00:00 2001 From: Nataya Price Date: Wed, 10 Apr 2024 20:43:54 +0000 Subject: [PATCH 4/4] fix: Fixed Patron files --- .../lesson16/web/CreatePatronRequest.java | 5 +- .../lesson16/web/CreatePatronResponse.java | 3 +- .../lesson16/web/GetPatronsResponse.java | 12 ++ .../lesson16/web/PatronRequest.java | 4 +- .../lesson16/web/PatronResponse.java | 19 +- .../lesson16/web/PatronsController.java | 16 +- .../lesson16/web/PatronsControllerTest.java | 167 +++++++++--------- 7 files changed, 121 insertions(+), 105 deletions(-) create mode 100644 lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/GetPatronsResponse.java diff --git a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/CreatePatronRequest.java b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/CreatePatronRequest.java index 4cf7cb540..ce1e58673 100644 --- a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/CreatePatronRequest.java +++ b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/CreatePatronRequest.java @@ -12,7 +12,6 @@ @NoArgsConstructor @Builder public class CreatePatronRequest { - @NotNull(message = "item is required") @Valid - private PatronRequest patron; - + @NotNull(message = "item is required") @Valid + private PatronRequest patron; } diff --git a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/CreatePatronResponse.java b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/CreatePatronResponse.java index f76c806ca..3799bed1a 100644 --- a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/CreatePatronResponse.java +++ b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/CreatePatronResponse.java @@ -6,6 +6,5 @@ @Data @Builder public class CreatePatronResponse { -private PatronResponse patron; - + private PatronResponse patron; } diff --git a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/GetPatronsResponse.java b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/GetPatronsResponse.java new file mode 100644 index 000000000..73bc429c6 --- /dev/null +++ b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/GetPatronsResponse.java @@ -0,0 +1,12 @@ +package com.codedifferently.lesson16.web; + +import java.util.List; +import lombok.Builder; +import lombok.Data; +import lombok.Singular; + +@Data +@Builder +public class GetPatronsResponse { + @Singular private List patronResponses; +} diff --git a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronRequest.java b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronRequest.java index 0aac28b69..ebd9e8c55 100644 --- a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronRequest.java +++ b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronRequest.java @@ -1,5 +1,3 @@ package com.codedifferently.lesson16.web; -public class PatronRequest { - -} +public class PatronRequest {} diff --git a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronResponse.java b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronResponse.java index 27498d5e0..9aa2ba601 100644 --- a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronResponse.java +++ b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronResponse.java @@ -1,5 +1,22 @@ package com.codedifferently.lesson16.web; +import com.codedifferently.lesson16.library.LibraryGuest; +import java.util.UUID; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder public class PatronResponse { - + private UUID id; + private String name; + private String email; + + public static PatronResponse from(LibraryGuest guest) { + return PatronResponse.builder() + .id(guest.getId()) + .name(guest.getName()) + .email(guest.getEmail()) + .build(); + } } diff --git a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsController.java b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsController.java index 3b263491a..0e105f7d6 100644 --- a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsController.java +++ b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsController.java @@ -2,8 +2,8 @@ import com.codedifferently.lesson16.library.Librarian; import com.codedifferently.lesson16.library.Library; +import com.codedifferently.lesson16.library.LibraryGuest; import com.codedifferently.lesson16.library.Patron; -import com.codedifferently.lesson16.library.search.PatronSearchCriteria; import java.io.IOException; import java.util.List; import java.util.Set; @@ -23,18 +23,18 @@ public PatronsController(Library library) throws IOException { } @GetMapping("/patrons") - public ResponseEntity> getPatrons() { + public ResponseEntity getPatrons() { Set guests = library.getPatrons(); - List patrons = new ArrayList<>(guests); - return ResponseEntity.ok(patrons); -} - + List patrons = guests.stream().map(PatronResponse::from).toList(); + var response = GetPatronsResponse.builder().patrons(patrons).build(); + return ResponseEntity.ok(response); + } @PostMapping("/patrons") - public ResponseEntity addPatron(@RequestBody PatronRequest patronRequest) { + public ResponseEntity addPatron(@RequestBody PatronRequest patronRequest) { Patron patron = PatronRequest.toPatron(patronRequest); library.addLibraryGuest(patron); - return ResponseEntity.status(HttpStatus.CREATED).body(GetPatronResponse.from(patron)); + return ResponseEntity.status(HttpStatus.CREATED).body(GetPatronsResponse.from(patron)); } @GetMapping("/patrons/{id}") diff --git a/lesson_16/api/api_app/src/test/java/com/codedifferently/lesson16/web/PatronsControllerTest.java b/lesson_16/api/api_app/src/test/java/com/codedifferently/lesson16/web/PatronsControllerTest.java index 862815beb..02cc594cf 100644 --- a/lesson_16/api/api_app/src/test/java/com/codedifferently/lesson16/web/PatronsControllerTest.java +++ b/lesson_16/api/api_app/src/test/java/com/codedifferently/lesson16/web/PatronsControllerTest.java @@ -5,106 +5,97 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import com.codedifferently.lesson16.Lesson16; import com.codedifferently.lesson16.library.Library; import com.codedifferently.lesson16.library.Patron; import com.codedifferently.lesson16.library.search.PatronSearchCriteria; +import java.util.Set; import org.junit.jupiter.api.BeforeEach; 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.http.MediaType; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -import java.util.Set; @SpringBootTest -@AutoConfigureMockMvc -@ContextConfiguration(classes = {Library.class, PatronsController.class}) +@ContextConfiguration(classes = Lesson16.class) class PatronsControllerTest { - @Autowired - private MockMvc mockMvc; - - @Autowired - private Library library; - - @BeforeEach - void setUp() { - library.clearPatrons(); // Clear patrons before each test - } - - @Test - void testController_getsAllPatrons() throws Exception { - mockMvc.perform(get("/patrons") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.patrons").isArray()); - } - - @Test - void testController_getsAPatron() throws Exception { - Patron patron = new Patron("John Doe"); - library.addLibraryGuest(patron); - - mockMvc.perform(get("/patrons/{id}", patron.getId()) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()); - } - - @Test - void testController_returnsNotFoundOnGetPatron() throws Exception { - mockMvc.perform(get("/patrons/{id}", "00000") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotFound()); - } - - @Test - void testController_reportsBadRequestOnAddPatron() throws Exception { - String json = "{}"; - - mockMvc.perform(post("/patrons") - .contentType(MediaType.APPLICATION_JSON) - .content(json)) - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.errors").isArray()) - .andExpect(jsonPath("$.errors.length()").value(1)); - } - - @Test - void testController_addsPatron() throws Exception { - String json = "{\"name\": \"John Doe\"}"; - - mockMvc.perform(post("/patrons") - .contentType(MediaType.APPLICATION_JSON) - .content(json)) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$.patron.id").isString()); - - Set patrons = library.searchPatrons(PatronSearchCriteria.builder().name("John Doe").build()); - assertThat(patrons).hasSize(1); - } - - @Test - void testController_returnsNotFoundOnDeletePatron() throws Exception { - mockMvc.perform(delete("/patrons/{id}", "00000") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotFound()); - } - - @Test - void testController_deletesPatron() throws Exception { - Patron patron = new Patron("John Doe"); - library.addLibraryGuest(patron); - - mockMvc.perform(delete("/patrons/{id}", patron.getId()) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isNoContent()); - - Set patrons = library.searchPatrons(PatronSearchCriteria.builder().id(patron.getId().toString()).build()); - assertThat(patrons).hasSize(0); - } + private static MockMvc mockMvc; + @Autowired private Library library; + + @BeforeEach + void setUp() {} + + @Test + void testController_getsAllPatrons() throws Exception { + mockMvc + .perform(get("/patrons").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.patrons").isArray()); + } + + @Test + void testController_getsAPatron() throws Exception { + Patron patron = new Patron("John Doe"); + library.addLibraryGuest(patron); + + mockMvc + .perform(get("/patrons/{id}", patron.getId()).contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } + + @Test + void testController_returnsNotFoundOnGetPatron() throws Exception { + mockMvc + .perform(get("/patrons/{id}", "00000").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + void testController_reportsBadRequestOnAddPatron() throws Exception { + String json = "{}"; + + mockMvc + .perform(post("/patrons").contentType(MediaType.APPLICATION_JSON).content(json)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.errors").isArray()) + .andExpect(jsonPath("$.errors.length()").value(1)); + } + + @Test + void testController_addsPatron() throws Exception { + String json = "{\"name\": \"John Doe\"}"; + + mockMvc + .perform(post("/patrons").contentType(MediaType.APPLICATION_JSON).content(json)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.patron.id").isString()); + + Set patrons = + library.searchPatrons(PatronSearchCriteria.builder().name("John Doe").build()); + assertThat(patrons).hasSize(1); + } + + @Test + void testController_returnsNotFoundOnDeletePatron() throws Exception { + mockMvc + .perform(delete("/patrons/{id}", "00000").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + void testController_deletesPatron() throws Exception { + Patron patron = new Patron("John Doe"); + library.addLibraryGuest(patron); + + mockMvc + .perform(delete("/patrons/{id}", patron.getId()).contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()); + + Set patrons = + library.searchPatrons(PatronSearchCriteria.builder().id(patron.getId().toString()).build()); + assertThat(patrons).hasSize(0); + } }