Compare commits
5 Commits
borrow
...
give-back-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19ce80db76 | ||
|
|
8a1e7ae20e | ||
|
|
d2b7d08072 | ||
|
|
2e5858d8a9 | ||
|
|
5e221dd427 |
@@ -0,0 +1,26 @@
|
||||
package io.wkrzywiec.hexagonal.library.domain;
|
||||
|
||||
import io.restassured.RestAssured;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.web.server.LocalServerPort;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
public abstract class BaseComponentTest {
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
protected String baseURL;
|
||||
|
||||
@Autowired
|
||||
protected JdbcTemplate jdbcTemplate;
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
this.baseURL = "http://localhost:" + port;
|
||||
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,20 @@
|
||||
package io.wkrzywiec.hexagonal.library.domain.borrowing;
|
||||
|
||||
import io.restassured.RestAssured;
|
||||
import io.wkrzywiec.hexagonal.library.BookTestData;
|
||||
import io.wkrzywiec.hexagonal.library.UserTestData;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BookReservationCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.BaseComponentTest;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BorrowBookCommand;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.web.server.LocalServerPort;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.test.context.jdbc.Sql;
|
||||
|
||||
import static io.restassured.RestAssured.given;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.AFTER_TEST_METHOD;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
public class BorrowBookComponentTest {
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
private String baseURL;
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
this.baseURL = "http://localhost:" + port;
|
||||
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
|
||||
}
|
||||
public class BorrowBookComponentTest extends BaseComponentTest {
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
@DisplayName("Borrow reserved book")
|
||||
@Sql({"/book-and-user.sql", "/available-book.sql"})
|
||||
@Sql(scripts = "/clean-database.sql", executionPhase = AFTER_TEST_METHOD)
|
||||
@@ -73,11 +50,11 @@ public class BorrowBookComponentTest {
|
||||
.prettyPeek()
|
||||
.then();
|
||||
|
||||
Long reservationId = jdbcTemplate.queryForObject(
|
||||
Long borrowId = jdbcTemplate.queryForObject(
|
||||
"SELECT id FROM borrowed WHERE book_id = ?",
|
||||
Long.class,
|
||||
homoDeusBookId);
|
||||
|
||||
assertTrue(reservationId > 0);
|
||||
assertTrue(borrowId > 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package io.wkrzywiec.hexagonal.library.domain.borrowing;
|
||||
|
||||
import io.wkrzywiec.hexagonal.library.BookTestData;
|
||||
import io.wkrzywiec.hexagonal.library.UserTestData;
|
||||
import io.wkrzywiec.hexagonal.library.domain.BaseComponentTest;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.GiveBackBookCommand;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.context.jdbc.Sql;
|
||||
|
||||
import static io.restassured.RestAssured.given;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.AFTER_TEST_METHOD;
|
||||
|
||||
public class GiveBackBookComponentTest extends BaseComponentTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("Give back borrowed book")
|
||||
@Sql({"/book-and-user.sql", "/available-book.sql"})
|
||||
@Sql(scripts = "/clean-database.sql", executionPhase = AFTER_TEST_METHOD)
|
||||
public void givenBookIsReserved_thenBorrowIt_thenBookIsBorrowed() {
|
||||
//given
|
||||
Long homoDeusBookId = jdbcTemplate.queryForObject(
|
||||
"SELECT id FROM book WHERE title = ?",
|
||||
Long.class,
|
||||
BookTestData.homoDeusBookTitle());
|
||||
|
||||
Long activeUserId = jdbcTemplate.queryForObject(
|
||||
"SELECT id FROM user WHERE email = ?",
|
||||
Long.class,
|
||||
UserTestData.johnDoeEmail());
|
||||
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO public.borrowed (book_id, user_id) VALUES (?, ?)",
|
||||
homoDeusBookId,
|
||||
activeUserId);
|
||||
|
||||
GiveBackBookCommand giveBackBookCommand =
|
||||
GiveBackBookCommand.builder()
|
||||
.bookId(homoDeusBookId )
|
||||
.userId(activeUserId)
|
||||
.build();
|
||||
|
||||
//when
|
||||
given()
|
||||
.contentType("application/json")
|
||||
.body(giveBackBookCommand)
|
||||
.when()
|
||||
.post( baseURL + "/giveBack")
|
||||
.prettyPeek()
|
||||
.then();
|
||||
|
||||
Long bookId = jdbcTemplate.queryForObject(
|
||||
"SELECT book_id FROM available WHERE book_id = ?",
|
||||
Long.class,
|
||||
homoDeusBookId);
|
||||
|
||||
assertEquals(homoDeusBookId, bookId);
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,24 @@
|
||||
package io.wkrzywiec.hexagonal.library.domain.borrowing;
|
||||
|
||||
import io.restassured.RestAssured;
|
||||
import io.wkrzywiec.hexagonal.library.BookTestData;
|
||||
import io.wkrzywiec.hexagonal.library.UserTestData;
|
||||
import io.wkrzywiec.hexagonal.library.domain.BaseComponentTest;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BookReservationCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.inventory.infrastructure.BookRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.web.server.LocalServerPort;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.test.context.jdbc.Sql;
|
||||
|
||||
import static io.restassured.RestAssured.given;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.AFTER_TEST_METHOD;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
public class MakeReservationComponentTest {
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
public class MakeReservationComponentTest extends BaseComponentTest {
|
||||
|
||||
@Autowired
|
||||
private BookRepository bookRepository;
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
private String baseURL;
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
this.baseURL = "http://localhost:" + port;
|
||||
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Reserve available book")
|
||||
@Sql({"/book-and-user.sql", "/available-book.sql"})
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
package io.wkrzywiec.hexagonal.library.domain.inventory;
|
||||
|
||||
import io.restassured.RestAssured;
|
||||
import io.restassured.response.ValidatableResponse;
|
||||
import io.wkrzywiec.hexagonal.library.BookTestData;
|
||||
import io.wkrzywiec.hexagonal.library.domain.BaseComponentTest;
|
||||
import io.wkrzywiec.hexagonal.library.domain.inventory.core.model.AddNewBookCommand;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.web.server.LocalServerPort;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.test.context.jdbc.Sql;
|
||||
|
||||
import static io.restassured.RestAssured.given;
|
||||
@@ -19,22 +14,7 @@ import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.AFTER_TEST_METHOD;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
public class AddNewBookComponentTest {
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbc;
|
||||
|
||||
private String baseURL;
|
||||
|
||||
@BeforeEach
|
||||
public void init(){
|
||||
this.baseURL = "http://localhost:" + port;
|
||||
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
|
||||
}
|
||||
public class AddNewBookComponentTest extends BaseComponentTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("Search for a new book in Google Books")
|
||||
@@ -73,14 +53,14 @@ public class AddNewBookComponentTest {
|
||||
.then();
|
||||
|
||||
//then
|
||||
Long savedBookId = jdbc.queryForObject(
|
||||
Long savedBookId = jdbcTemplate.queryForObject(
|
||||
"SELECT id FROM book WHERE book_external_id = ?",
|
||||
Long.class,
|
||||
BookTestData.homoDeusBookGoogleId());
|
||||
|
||||
assertTrue(savedBookId > 0);
|
||||
|
||||
Long availableBookId = jdbc.queryForObject(
|
||||
Long availableBookId = jdbcTemplate.queryForObject(
|
||||
"SELECT id FROM available WHERE book_id = ?",
|
||||
Long.class,
|
||||
savedBookId);
|
||||
|
||||
@@ -1,36 +1,16 @@
|
||||
package io.wkrzywiec.hexagonal.library.domain.user;
|
||||
|
||||
import io.restassured.RestAssured;
|
||||
import io.wkrzywiec.hexagonal.library.domain.BaseComponentTest;
|
||||
import io.wkrzywiec.hexagonal.library.domain.user.core.model.AddUserCommand;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.web.server.LocalServerPort;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.test.context.jdbc.Sql;
|
||||
|
||||
import static io.restassured.RestAssured.given;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.AFTER_TEST_METHOD;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
public class AddNewUserComponentTest {
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
private String baseURL;
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
@BeforeEach
|
||||
public void init(){
|
||||
baseURL = "http://localhost:" + port;
|
||||
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
|
||||
}
|
||||
public class AddNewUserComponentTest extends BaseComponentTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("Create new user")
|
||||
|
||||
@@ -22,6 +22,6 @@ public class BorrowBookController {
|
||||
@PostMapping("")
|
||||
public ResponseEntity<String> borrowBook(@RequestBody BorrowBookCommand borrowBookCommand){
|
||||
borrowBook.handle(borrowBookCommand);
|
||||
return new ResponseEntity<>("Book with an id " + borrowBookCommand.getBookId() + " was borrowed", HttpStatus.CREATED);
|
||||
return new ResponseEntity<>("Book with an id " + borrowBookCommand.getBookId() + " was borrowed", HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.wkrzywiec.hexagonal.library.domain.borrowing.application;
|
||||
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.GiveBackBookCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming.GiveBackBook;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
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;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/giveBack")
|
||||
@RequiredArgsConstructor
|
||||
public class GiveBackController {
|
||||
|
||||
@Qualifier("GiveBackBook")
|
||||
private final GiveBackBook giveBackBook;
|
||||
|
||||
@PostMapping("")
|
||||
public ResponseEntity<String> giveBack(@RequestBody GiveBackBookCommand giveBackBookCommand){
|
||||
giveBackBook.handle(giveBackBookCommand);
|
||||
return new ResponseEntity<>("Book with an id " + giveBackBookCommand.getBookId() + " was returned", HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,6 @@ public class ReservationController {
|
||||
@PostMapping("")
|
||||
public ResponseEntity<String> makeReservation(@RequestBody BookReservationCommand reservationCommand){
|
||||
Long reservationId = reserveBook.handle(reservationCommand);
|
||||
return new ResponseEntity<>("Reservation has been made with an id " + reservationId, HttpStatus.CREATED);
|
||||
return new ResponseEntity<>("Reservation has been made with an id " + reservationId, HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,15 +7,18 @@ import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BookReservedEv
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BorrowBookCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BorrowedBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.DueDate;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.GiveBackBookCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.MakeBookAvailableCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.OverdueReservation;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ReservationDetails;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ReservedBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.exception.ActiveUserNotFoundException;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.exception.AvailableBookNotFoundExeption;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.exception.BorrowedBookNotFoundException;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.exception.ReservedBookNotFoundException;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming.BorrowBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming.CancelOverdueReservations;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming.GiveBackBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming.MakeBookAvailable;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming.ReserveBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.outgoing.BorrowingDatabase;
|
||||
@@ -24,9 +27,8 @@ import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.outgoing.Borro
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class BorrowingFacade implements MakeBookAvailable, ReserveBook, CancelOverdueReservations, BorrowBook {
|
||||
public class BorrowingFacade implements MakeBookAvailable, ReserveBook, CancelOverdueReservations, BorrowBook, GiveBackBook {
|
||||
|
||||
private final BorrowingDatabase database;
|
||||
private final BorrowingEventPublisher eventPublisher;
|
||||
@@ -38,7 +40,7 @@ public class BorrowingFacade implements MakeBookAvailable, ReserveBook, CancelOv
|
||||
|
||||
@Override
|
||||
public void handle(MakeBookAvailableCommand bookAvailableCommand) {
|
||||
database.setBookAvailable(bookAvailableCommand.getBookId());
|
||||
database.save(new AvailableBook(bookAvailableCommand.getBookId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,7 +64,7 @@ public class BorrowingFacade implements MakeBookAvailable, ReserveBook, CancelOv
|
||||
DueDate dueDate = new DueDate(Instant.now().plus(3L, ChronoUnit.DAYS));
|
||||
List<OverdueReservation> overdueReservationList = database.findReservationsAfter(dueDate);
|
||||
overdueReservationList.forEach(
|
||||
overdue -> database.setBookAvailable(overdue.getBookIdentificationAsLong()));
|
||||
overdue -> database.save(new AvailableBook(overdue.getBookIdentificationAsLong())));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -77,4 +79,17 @@ public class BorrowingFacade implements MakeBookAvailable, ReserveBook, CancelOv
|
||||
BorrowedBook borrowedBook = activeUser.borrow(reservedBook);
|
||||
database.save(borrowedBook);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(GiveBackBookCommand command) {
|
||||
BorrowedBook borrowedBook =
|
||||
database.getBorrowedBook(command.getBookId())
|
||||
.orElseThrow(() -> new BorrowedBookNotFoundException(command.getBookId()));
|
||||
ActiveUser activeUser =
|
||||
database.getActiveUser(command.getUserId())
|
||||
.orElseThrow(() -> new ActiveUserNotFoundException(command.getUserId()));
|
||||
|
||||
AvailableBook availableBook = activeUser.giveBack(borrowedBook);
|
||||
database.save(availableBook);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,15 @@ public class ActiveUser {
|
||||
}
|
||||
}
|
||||
|
||||
public AvailableBook giveBack(BorrowedBook borrowedBook) {
|
||||
boolean isBookRemovedFromUserAccount = borrowedBooks.removeIf(book -> book.equals(borrowedBook));
|
||||
if (isBookRemovedFromUserAccount){
|
||||
return new AvailableBook(borrowedBook.getIdAsLong());
|
||||
} else {
|
||||
throw new IllegalArgumentException("User with an id: " + id + " didn't borrow book with an id: " + borrowedBook.getIdAsLong());
|
||||
}
|
||||
}
|
||||
|
||||
public Long getIdAsLong(){
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -7,9 +7,10 @@ import java.time.Instant;
|
||||
@EqualsAndHashCode
|
||||
public class BorrowedBook implements Book {
|
||||
|
||||
private final Long bookId;
|
||||
private final Long userId;
|
||||
private final Instant borrowedDate;
|
||||
private Long bookId;
|
||||
private Long userId;
|
||||
@EqualsAndHashCode.Exclude
|
||||
private Instant borrowedDate;
|
||||
|
||||
public BorrowedBook(Long bookId, Long userId) {
|
||||
this.bookId = bookId;
|
||||
@@ -17,6 +18,12 @@ public class BorrowedBook implements Book {
|
||||
this.borrowedDate = Instant.now();
|
||||
}
|
||||
|
||||
public BorrowedBook(Long bookId, Long userId, Instant borrowedDate) {
|
||||
this.bookId = bookId;
|
||||
this.userId = userId;
|
||||
this.borrowedDate = borrowedDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getIdAsLong() {
|
||||
return bookId;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.wkrzywiec.hexagonal.library.domain.borrowing.core.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Builder
|
||||
public class GiveBackBookCommand {
|
||||
private Long bookId;
|
||||
private Long userId;
|
||||
}
|
||||
@@ -17,6 +17,12 @@ public class ReservedBook implements Book {
|
||||
this.reservedDate = Instant.now();
|
||||
}
|
||||
|
||||
public ReservedBook(Long bookId, Long userId, Instant reservedDate) {
|
||||
this.bookId = bookId;
|
||||
this.userId = userId;
|
||||
this.reservedDate = reservedDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getIdAsLong() {
|
||||
return bookId;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.exception;
|
||||
|
||||
public class BorrowedBookNotFoundException extends RuntimeException {
|
||||
public BorrowedBookNotFoundException(Long bookId) {
|
||||
super("There is no borrowed book with an ID: " + bookId,
|
||||
null,
|
||||
false,
|
||||
false);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
package io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming;
|
||||
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.GiveBackBookCommand;
|
||||
|
||||
public interface GiveBackBook {
|
||||
void handle(GiveBackBookCommand command);
|
||||
}
|
||||
|
||||
@@ -12,11 +12,12 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface BorrowingDatabase {
|
||||
void setBookAvailable(Long bookId);
|
||||
Optional<AvailableBook> getAvailableBook(Long bookId);
|
||||
Optional<ActiveUser> getActiveUser(Long userId);
|
||||
void save(AvailableBook availableBook);
|
||||
ReservationDetails save(ReservedBook reservedBook);
|
||||
void save(BorrowedBook borrowedBook);
|
||||
Optional<AvailableBook> getAvailableBook(Long bookId);
|
||||
Optional<ActiveUser> getActiveUser(Long userId);
|
||||
List<OverdueReservation> findReservationsAfter(DueDate dueDate);
|
||||
Optional<ReservedBook> getReservedBook(Long bookId);
|
||||
Optional<BorrowedBook> getBorrowedBook(Long bookId);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ReservationDet
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ReservationId;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ReservedBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.outgoing.BorrowingDatabase;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.infrastructure.mapper.BorrowedBookRowMapper;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.infrastructure.mapper.ReservedBookRowMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.jdbc.core.BeanPropertyRowMapper;
|
||||
@@ -27,18 +29,18 @@ public class BorrowingDatabaseAdapter implements BorrowingDatabase {
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Override
|
||||
public void setBookAvailable(Long bookId) {
|
||||
public void save(AvailableBook availableBook) {
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO available (book_id) VALUES (?)",
|
||||
bookId);
|
||||
availableBook.getIdAsLong());
|
||||
|
||||
jdbcTemplate.update(
|
||||
"DELETE FROM reserved WHERE book_id = ?",
|
||||
bookId);
|
||||
availableBook.getIdAsLong());
|
||||
|
||||
jdbcTemplate.update(
|
||||
"DELETE FROM borrowed WHERE book_id = ?",
|
||||
bookId);
|
||||
availableBook.getIdAsLong());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -124,7 +126,20 @@ public class BorrowingDatabaseAdapter implements BorrowingDatabase {
|
||||
return Optional.ofNullable(
|
||||
jdbcTemplate.queryForObject(
|
||||
"SELECT book_id AS bookId, user_id AS userId, reserved_date AS reservedDate FROM reserved WHERE reserved.book_id = ?",
|
||||
ReservedBook.class,
|
||||
new ReservedBookRowMapper(),
|
||||
bookId));
|
||||
} catch (DataAccessException exception) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<BorrowedBook> getBorrowedBook(Long bookId) {
|
||||
try {
|
||||
return Optional.ofNullable(
|
||||
jdbcTemplate.queryForObject(
|
||||
"SELECT book_id, user_id, borrowed_date FROM borrowed WHERE book_id = ?",
|
||||
new BorrowedBookRowMapper(),
|
||||
bookId));
|
||||
} catch (DataAccessException exception) {
|
||||
return Optional.empty();
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.wkrzywiec.hexagonal.library.domain.borrowing.infrastructure.mapper;
|
||||
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BorrowedBook;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
|
||||
public class BorrowedBookRowMapper implements RowMapper<BorrowedBook> {
|
||||
|
||||
@Override
|
||||
public BorrowedBook mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
return new BorrowedBook(
|
||||
rs.getLong("book_id"),
|
||||
rs.getLong("user_id"),
|
||||
rs.getTimestamp("borrowed_date").toInstant()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package io.wkrzywiec.hexagonal.library.domain.borrowing.infrastructure.mapper;
|
||||
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ReservedBook;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class ReservedBookRowMapper implements RowMapper<ReservedBook> {
|
||||
|
||||
@Override
|
||||
public ReservedBook mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
return new ReservedBook(
|
||||
rs.getLong("book_id"),
|
||||
rs.getLong("user_id"),
|
||||
rs.getTimestamp("reserved_date").toInstant()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package io.wkrzywiec.hexagonal.library.infrastructure;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.BorrowingFacade;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming.BorrowBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming.CancelOverdueReservations;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming.GiveBackBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming.MakeBookAvailable;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming.ReserveBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.outgoing.BorrowingDatabase;
|
||||
@@ -12,7 +13,6 @@ import io.wkrzywiec.hexagonal.library.domain.borrowing.infrastructure.SpringBorr
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
public class BorrowingDomainConfig {
|
||||
@@ -45,6 +45,12 @@ public class BorrowingDomainConfig {
|
||||
return new BorrowingFacade(database, borrowingEventPublisher);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Qualifier("GiveBackBook")
|
||||
public GiveBackBook giveBackBook(BorrowingDatabase database, BorrowingEventPublisher borrowingEventPublisher){
|
||||
return new BorrowingFacade(database, borrowingEventPublisher);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Qualifier("CancelOverdueReservations")
|
||||
public CancelOverdueReservations cancelOverdueReservations(BorrowingDatabase database, BorrowingEventPublisher borrowingEventPublisher){
|
||||
|
||||
@@ -3,9 +3,11 @@ package io.wkrzywiec.hexagonal.library.domain.borrowing;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ActiveUser;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BorrowBookCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BorrowedBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.GiveBackBookCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ReservedBook;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class BorrowTestData {
|
||||
|
||||
@@ -16,11 +18,31 @@ public class BorrowTestData {
|
||||
.build();
|
||||
}
|
||||
|
||||
public static GiveBackBookCommand anyGiveBookCommand(Long bookId, Long userId){
|
||||
return GiveBackBookCommand.builder()
|
||||
.bookId(bookId)
|
||||
.userId(userId)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static ReservedBook anyReservedBook(Long bookId, Long userId){
|
||||
return new ReservedBook(bookId, userId);
|
||||
}
|
||||
|
||||
public static BorrowedBook anyBorrowedBook(Long bookId, Long userId){
|
||||
return new BorrowedBook(bookId, userId);
|
||||
}
|
||||
|
||||
public static ActiveUser anyActiveUser(Long userId){
|
||||
return new ActiveUser(userId, new ArrayList<ReservedBook>(), new ArrayList<BorrowedBook>());
|
||||
}
|
||||
|
||||
public static ActiveUser anyActiveUserWithReservedBooks(Long userId, List<ReservedBook> reservedBookList){
|
||||
return new ActiveUser(userId, reservedBookList, new ArrayList<BorrowedBook>());
|
||||
}
|
||||
|
||||
public static ActiveUser anyActiveUserWithBorrowedBooks(Long userId, List<BorrowedBook> borrowedBooksList){
|
||||
return new ActiveUser(userId, new ArrayList<ReservedBook>(), borrowedBooksList);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ActiveUser;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.AvailableBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BookReservationCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BorrowBookCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BorrowedBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.GiveBackBookCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.MakeBookAvailableCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ReservedBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.exception.ActiveUserNotFoundException;
|
||||
@@ -18,6 +20,9 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
@@ -188,4 +193,24 @@ public class BorrowingFacadeTest {
|
||||
//then
|
||||
assertEquals(1, activeUser.getBorrowedBookList().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Successful give back a book")
|
||||
public void givenUserWithBorrowedBook_whenBookIsReturned_thenBookIsAvailable(){
|
||||
//given
|
||||
GiveBackBookCommand giveBackBookCommand = BorrowTestData.anyGiveBookCommand(100L, 100L);
|
||||
BorrowedBook borrowedBook = BorrowTestData.anyBorrowedBook(giveBackBookCommand.getBookId(), giveBackBookCommand.getUserId());
|
||||
ActiveUser activeUser = BorrowTestData.anyActiveUserWithBorrowedBooks(giveBackBookCommand.getUserId(), new ArrayList<BorrowedBook>(Arrays.asList(borrowedBook)));
|
||||
|
||||
database.borrowedBooks.put(borrowedBook.getIdAsLong(), borrowedBook);
|
||||
database.activeUsers.put(activeUser.getIdAsLong(), activeUser);
|
||||
|
||||
//when
|
||||
facade.handle(giveBackBookCommand);
|
||||
|
||||
//then
|
||||
assertEquals(0, database.borrowedBooks.size());
|
||||
assertEquals(1, database.availableBooks.size());
|
||||
assertEquals(0, database.activeUsers.get(activeUser.getIdAsLong()).getBorrowedBookList().size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,10 @@ public class InMemoryBorrowingDatabase implements BorrowingDatabase {
|
||||
ConcurrentHashMap<Long, BorrowedBook> borrowedBooks = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void setBookAvailable(Long bookId) {
|
||||
availableBooks.put(bookId, new AvailableBook(bookId));
|
||||
reservedBooks.remove(bookId);
|
||||
public void save(AvailableBook availableBook) {
|
||||
availableBooks.put(availableBook.getIdAsLong(), availableBook);
|
||||
reservedBooks.remove(availableBook.getIdAsLong());
|
||||
borrowedBooks.remove(availableBook.getIdAsLong());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -78,4 +79,9 @@ public class InMemoryBorrowingDatabase implements BorrowingDatabase {
|
||||
public Optional<ReservedBook> getReservedBook(Long bookId) {
|
||||
return Optional.of(reservedBooks.get(bookId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<BorrowedBook> getBorrowedBook(Long bookId) {
|
||||
return Optional.of(borrowedBooks.get(bookId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,10 +45,10 @@ public class BorrowingDatabaseAdapterITCase {
|
||||
@Sql(scripts = "/clean-database.sql", executionPhase = AFTER_TEST_METHOD)
|
||||
public void shouldSaveAvailableBook(){
|
||||
//given
|
||||
Long bookId = getHomoDeusBookId();
|
||||
Long bookId = getHomoDeusBookIdFromDb();
|
||||
|
||||
//when
|
||||
database.setBookAvailable(bookId);
|
||||
database.save(new AvailableBook(bookId));
|
||||
|
||||
//then
|
||||
Long savedBookId = jdbcTemplate.queryForObject(
|
||||
@@ -64,7 +64,7 @@ public class BorrowingDatabaseAdapterITCase {
|
||||
@Sql(scripts = "/clean-database.sql", executionPhase = AFTER_TEST_METHOD)
|
||||
public void shouldGetAvailableBook(){
|
||||
//given
|
||||
Long bookId = getHomoDeusBookId();
|
||||
Long bookId = getHomoDeusBookIdFromDb();
|
||||
|
||||
//when
|
||||
Optional<AvailableBook> availableBookOptional = database.getAvailableBook(bookId);
|
||||
@@ -80,7 +80,7 @@ public class BorrowingDatabaseAdapterITCase {
|
||||
@Sql(scripts = "/clean-database.sql", executionPhase = AFTER_TEST_METHOD)
|
||||
public void shouldGetActiveUser() {
|
||||
//given
|
||||
Long activeUserId = getJohnDoeUserId();
|
||||
Long activeUserId = getJohnDoeUserIdFromDb();
|
||||
|
||||
//when
|
||||
Optional<ActiveUser> activeUserOptional = database.getActiveUser(activeUserId);
|
||||
@@ -96,9 +96,9 @@ public class BorrowingDatabaseAdapterITCase {
|
||||
@Sql(scripts = "/clean-database.sql", executionPhase = AFTER_TEST_METHOD)
|
||||
public void shouldSaveReservedBook(){
|
||||
//given
|
||||
Long bookId = getHomoDeusBookId();
|
||||
Long bookId = getHomoDeusBookIdFromDb();
|
||||
|
||||
Long activeUserId = getJohnDoeUserId();
|
||||
Long activeUserId = getJohnDoeUserIdFromDb();
|
||||
|
||||
ReservedBook reservedBook = new ReservedBook(bookId, activeUserId);
|
||||
|
||||
@@ -112,14 +112,13 @@ public class BorrowingDatabaseAdapterITCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
@DisplayName("Get reserved book by its id")
|
||||
@Sql({"/book-and-user.sql"})
|
||||
@Sql(scripts = "/clean-database.sql", executionPhase = AFTER_TEST_METHOD)
|
||||
public void shouldFindReservedBook(){
|
||||
//given
|
||||
Long bookId = getHomoDeusBookId();
|
||||
Long johnDoeUserId = getJohnDoeUserId();
|
||||
Long bookId = getHomoDeusBookIdFromDb();
|
||||
Long johnDoeUserId = getJohnDoeUserIdFromDb();
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO public.reserved (book_id, user_id) VALUES (?, ?)",
|
||||
bookId,
|
||||
@@ -139,8 +138,8 @@ public class BorrowingDatabaseAdapterITCase {
|
||||
@Sql(scripts = "/clean-database.sql", executionPhase = AFTER_TEST_METHOD)
|
||||
public void shouldSaveBorrowedBook(){
|
||||
//given
|
||||
Long bookId = getHomoDeusBookId();
|
||||
Long activeUserId = getJohnDoeUserId();
|
||||
Long bookId = getHomoDeusBookIdFromDb();
|
||||
Long activeUserId = getJohnDoeUserIdFromDb();
|
||||
|
||||
BorrowedBook borrowedBook = new BorrowedBook(bookId, activeUserId);
|
||||
|
||||
@@ -162,13 +161,13 @@ public class BorrowingDatabaseAdapterITCase {
|
||||
public void shouldFindOverdueReservations(){
|
||||
//given
|
||||
DueDate thirdDayAfterReservation = new DueDate(Instant.now().plus(3, ChronoUnit.DAYS));
|
||||
Long overdueBookId = getHomoDeusBookId();
|
||||
Long johnDoeUserId = getJohnDoeUserId();
|
||||
Long overdueBookId = getHomoDeusBookIdFromDb();
|
||||
Long johnDoeUserId = getJohnDoeUserIdFromDb();
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO public.reserved (book_id, user_id, reserved_date) VALUES (?, ?, ?)",
|
||||
overdueBookId,
|
||||
johnDoeUserId,
|
||||
Instant.now().plus(3, ChronoUnit.DAYS));
|
||||
Instant.now().plus(4, ChronoUnit.DAYS));
|
||||
|
||||
//when
|
||||
OverdueReservation overdueReservation = database.findReservationsAfter(thirdDayAfterReservation).get(0);
|
||||
@@ -177,14 +176,38 @@ public class BorrowingDatabaseAdapterITCase {
|
||||
assertEquals(overdueBookId, overdueReservation.getBookIdentificationAsLong());
|
||||
}
|
||||
|
||||
private Long getHomoDeusBookId(){
|
||||
@Test
|
||||
@DisplayName("Find borrowed book by id")
|
||||
@Sql({"/book-and-user.sql"})
|
||||
@Sql(scripts = "/clean-database.sql", executionPhase = AFTER_TEST_METHOD)
|
||||
public void shouldFindBorrowedBook(){
|
||||
//given
|
||||
Long bookId = getHomoDeusBookIdFromDb();
|
||||
Long johnDoeUserId = getJohnDoeUserIdFromDb();
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO public.borrowed (book_id, user_id, borrowed_date) VALUES (?, ?, ?)",
|
||||
bookId,
|
||||
johnDoeUserId,
|
||||
Instant.now());
|
||||
|
||||
Long id = jdbcTemplate.queryForObject("SELECT book_id FROM borrowed WHERE book_id = ?", Long.class, bookId);
|
||||
|
||||
//when
|
||||
Optional<BorrowedBook> borrowedBook = database.getBorrowedBook(bookId);
|
||||
|
||||
//then
|
||||
assertTrue(borrowedBook.isPresent());
|
||||
assertEquals(bookId, borrowedBook.get().getIdAsLong());
|
||||
}
|
||||
|
||||
private Long getHomoDeusBookIdFromDb(){
|
||||
return jdbcTemplate.queryForObject(
|
||||
"SELECT id FROM book WHERE title = ?",
|
||||
Long.class,
|
||||
BookTestData.homoDeusBookTitle());
|
||||
}
|
||||
|
||||
private Long getJohnDoeUserId(){
|
||||
private Long getJohnDoeUserIdFromDb(){
|
||||
return jdbcTemplate.queryForObject(
|
||||
"SELECT id FROM user WHERE email = ?",
|
||||
Long.class,
|
||||
|
||||
Reference in New Issue
Block a user