Skip to content

Commit

Permalink
Merge pull request #6 from neuefische/Feat/Add-book
Browse files Browse the repository at this point in the history
Feat/add-book (POST method in backend, create form in frontend)
  • Loading branch information
rinaehyun authored Aug 15, 2024
2 parents 90d4483 + 32c9608 commit 896f955
Show file tree
Hide file tree
Showing 12 changed files with 250 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.github.esgoet.backend.controllers;

import com.github.esgoet.backend.dto.NewBookDto;
import com.github.esgoet.backend.models.Book;
import com.github.esgoet.backend.services.BookService;
import lombok.RequiredArgsConstructor;
Expand All @@ -10,7 +11,6 @@
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.NoSuchElementException;

@RestController
@RequestMapping("/api/books")
Expand All @@ -34,4 +34,9 @@ public void deleteBook(@PathVariable String id) {
public Book getBook(@PathVariable String id) {
return bookService.getBook(id);
}

@PostMapping
public Book addABook(@RequestBody NewBookDto newBookDto) {
return bookService.saveBook(newBookDto);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.github.esgoet.backend.dto;

import com.github.esgoet.backend.models.Genre;
import lombok.With;

import java.time.LocalDate;

@With
public record NewBookDto(
String author,
String title,
Genre genre,
LocalDate publicationDate
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@
import lombok.With;
import org.springframework.data.mongodb.core.mapping.Document;

import java.time.LocalDate;

@With
@Document("books")
public record Book(
String id,
String author,
String title) {
String title,
Genre genre,
LocalDate publicationDate
) {
}
19 changes: 19 additions & 0 deletions backend/src/main/java/com/github/esgoet/backend/models/Genre.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.github.esgoet.backend.models;

public enum Genre {
NONE("none"),
FICTION("Fiction"),
MYSTERY("Mystery"),
THRILLER("Thriller"),
FANTASY("Fantasy"),
SCIENCE("Science"),
NON_FICTION("Non-fiction"),
HISTORY("History"),
NOVEL("Novel");

private final String genreValue;

Genre(String genreValue) { this.genreValue = genreValue; }

public String getGenre() { return genreValue; }
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.github.esgoet.backend.services;

import com.github.esgoet.backend.dto.NewBookDto;
import com.github.esgoet.backend.models.Book;
import com.github.esgoet.backend.models.BookNotFoundException;
import com.github.esgoet.backend.repositories.BookRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;


import java.util.List;


Expand All @@ -15,6 +15,7 @@
public class BookService {

private final BookRepository bookRepository;
private final IdService idService;

public List<Book> getAllBooks() {
return bookRepository.findAll();
Expand All @@ -28,4 +29,15 @@ public Book getBook(String id) {
return bookRepository.findById(id)
.orElseThrow(() -> new BookNotFoundException("No book found with id: " + id));
}

public Book saveBook(NewBookDto newBookDto) {
Book bookToSave = new Book(
idService.randomId(),
newBookDto.author(),
newBookDto.title(),
newBookDto.genre(),
newBookDto.publicationDate()
);
return bookRepository.save(bookToSave);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.github.esgoet.backend.services;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.UUID;

@Service
@RequiredArgsConstructor
public class IdService {

public String randomId() {
return UUID.randomUUID().toString();
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package com.github.esgoet.backend.controllers;

import com.github.esgoet.backend.models.Book;
import com.github.esgoet.backend.models.Genre;
import com.github.esgoet.backend.repositories.BookRepository;
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.annotation.DirtiesContext;
import org.springframework.test.web.servlet.MockMvc;

import java.time.LocalDate;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;

Expand All @@ -21,6 +26,8 @@ class BookControllerIntegrationTest {
@Autowired
BookRepository bookRepository;

private final LocalDate localDate = LocalDate.parse("2024-08-14");

@Test
public void getAllBooks_Test_When_DbEmpty_Then_returnEmptyArray() throws Exception {

Expand All @@ -34,7 +41,7 @@ public void getAllBooks_Test_When_DbEmpty_Then_returnEmptyArray() throws Excepti
@Test
void getBook_Test_whenIdExists() throws Exception {
//GIVEN
bookRepository.save(new Book("1","George Orwell", "1984"));
bookRepository.save(new Book("1","George Orwell", "1984", Genre.FANTASY, localDate));
//WHEN
mockMvc.perform(get("/api/books/1"))
//THEN
Expand All @@ -43,11 +50,38 @@ void getBook_Test_whenIdExists() throws Exception {
{
"id": "1",
"author": "George Orwell",
"title": "1984"
"title": "1984",
"genre": "FANTASY",
"publicationDate": "2024-08-14"
}
"""));
}

@Test
@DirtiesContext
void addABookTest_whenNewTodoExists_thenReturnNewTodo() throws Exception {
// GIVEN

// WHEN
mockMvc.perform(post("/api/books")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"author": "Tolstoy",
"title": "War and Peace",
"genre": "HISTORY",
"publicationDate": "1869-01-01"
}
"""))
// THEN
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").exists())
.andExpect(jsonPath("$.author").value("Tolstoy"))
.andExpect(jsonPath("$.title").value("War and Peace"))
.andExpect(jsonPath("$.genre").value("HISTORY"))
.andExpect(jsonPath("$.publicationDate").value("1869-01-01"));
}

@DirtiesContext
@Test
void getBook_Test_whenIdDoesNotExists() throws Exception {
Expand All @@ -67,7 +101,7 @@ void getBook_Test_whenIdDoesNotExists() throws Exception {
@Test
public void deleteBook() throws Exception {

bookRepository.save(new Book("1", "Simon", "HowToDeleteBooksFast"));
bookRepository.save(new Book("1", "Simon", "HowToDeleteBooksFast", Genre.SCIENCE,localDate));

mockMvc.perform(delete("/api/books/1"))
.andExpect(status().isOk());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.github.esgoet.backend.services;

import com.github.esgoet.backend.dto.NewBookDto;
import com.github.esgoet.backend.models.Book;
import com.github.esgoet.backend.models.BookNotFoundException;
import com.github.esgoet.backend.models.Genre;
import com.github.esgoet.backend.repositories.BookRepository;
import org.junit.jupiter.api.Test;


import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
Expand All @@ -17,18 +18,20 @@
class BookServiceUnitTest {

private final BookRepository bookRepo = mock(BookRepository.class);
private final BookService bookService = new BookService(bookRepo);
private final IdService idService = mock(IdService.class);
private final BookService bookService = new BookService(bookRepo, idService);
private final LocalDate localDate = LocalDate.parse("2024-08-14");

@Test
void getAllBooks_Test() {
List<Book> allBooks = List.of(
new Book("1", "Simon", "Java for Dummies"),
new Book("2","Florian", "Java not for Dummies")
new Book("1", "Simon", "Java for Dummies", Genre.SCIENCE, localDate),
new Book("2","Florian", "Java not for Dummies", Genre.SCIENCE, localDate)
);

List<Book> expectedBooks = List.of(
new Book("1", "Simon", "Java for Dummies"),
new Book("2","Florian", "Java not for Dummies")
new Book("1", "Simon", "Java for Dummies", Genre.SCIENCE, localDate),
new Book("2","Florian", "Java not for Dummies", Genre.SCIENCE, localDate)
);

when(bookRepo.findAll()).thenReturn(allBooks);
Expand All @@ -49,12 +52,12 @@ void getAllBooks_WhenEmpty_ReturnsEmptyList() {
@Test
void getBook_Test_whenBookExists_thenReturnBook() {
//GIVEN
Book book = new Book("1", "George Orwell", "1984");
Book book = new Book("1", "George Orwell", "1984", Genre.FANTASY, localDate);
when(bookRepo.findById("1")).thenReturn(Optional.of(book));
//WHEN
Book actual = bookService.getBook("1");
//THEN
Book expected = new Book("1", "George Orwell", "1984");
Book expected = new Book("1", "George Orwell", "1984", Genre.FANTASY, localDate);
verify(bookRepo).findById("1");
assertEquals(expected, actual);
}
Expand All @@ -69,7 +72,23 @@ void getBook_Test_whenBookDoesNotExists_thenThrow() {
verify(bookRepo).findById("1");
}


@Test
void addABookTest_whenNewBookAsInput_thenReturnNewBook() {
// GIVEN
NewBookDto newBookDto = new NewBookDto("J. K. Rowling", "Harry Potter", Genre.FANTASY, localDate);
Book bookToSave = new Book("1", newBookDto.author(), newBookDto.title(), newBookDto.genre(), newBookDto.publicationDate());
when(bookRepo.save(bookToSave)).thenReturn(bookToSave);
when(idService.randomId()).thenReturn(bookToSave.id());

// WHEN
Book actual = bookService.saveBook(newBookDto);

// THEN
Book expected = new Book("1", newBookDto.author(), newBookDto.title(), newBookDto.genre(), newBookDto.publicationDate());
verify(bookRepo).save(bookToSave);
verify(idService).randomId();
assertEquals(expected, actual);
}

@Test
void deleteBook_Test() {
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {useEffect, useState} from "react";
import {Link, Route, Routes} from "react-router-dom";
import BookDetailsPage from "./pages/BookDetailsPage/bookDetailsPage/BookDetailsPage.tsx";
import BookGalleryPage from "./pages/BookGalleryPage/bookGalleryPage/BookGalleryPage.tsx";
import AddBookForm from "./pages/BookGalleryPage/components/addBookButton/AddBookForm.tsx";
import Header from "./components/header/Header.tsx";


Expand Down Expand Up @@ -41,6 +42,7 @@ function App() {
<Link to={"/books"}>All Books</Link>
<Routes>
<Route path={"/books"} element={<BookGalleryPage data={data}/>}/>
<Route path={"/books/add"} element={<AddBookForm fetchBooks={fetchBooks}/>}/>
<Route path={"/books/:id"} element={<BookDetailsPage deleteBook={deleteBook}/>}/>
</Routes>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import {Book} from "../../../types/types.ts";
import BookGallery from "../components/bookGallery/BookGallery.tsx";
import "./BookGalleryPage.css";
import {Link} from "react-router-dom";

type BookGalleryPageProps = {
data: Book[]
}
export default function BookGalleryPage({data}: BookGalleryPageProps) {
return (
<>
<Link to={"/books/add"}>Add a Book</Link>
<BookGallery data={data} />
</>
);
Expand Down
Loading

0 comments on commit 896f955

Please sign in to comment.