From 1e09749bc1873e357bd56635bd0a07564b9936b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB?= Date: Fri, 30 May 2025 01:40:42 +0300 Subject: [PATCH 1/4] pivo-102-hamm1ng --- .../ttv/poltoraha/pivka/repository/QuoteRepository.java | 8 ++++++++ .../poltoraha/pivka/serviceImpl/ReaderServiceImpl.java | 6 +++++- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 src/main/java/ttv/poltoraha/pivka/repository/QuoteRepository.java diff --git a/src/main/java/ttv/poltoraha/pivka/repository/QuoteRepository.java b/src/main/java/ttv/poltoraha/pivka/repository/QuoteRepository.java new file mode 100644 index 0000000..8a65d21 --- /dev/null +++ b/src/main/java/ttv/poltoraha/pivka/repository/QuoteRepository.java @@ -0,0 +1,8 @@ +package ttv.poltoraha.pivka.repository; + +import org.springframework.data.repository.CrudRepository; +import ttv.poltoraha.pivka.entity.Quote; + +public interface QuoteRepository extends CrudRepository { + +} diff --git a/src/main/java/ttv/poltoraha/pivka/serviceImpl/ReaderServiceImpl.java b/src/main/java/ttv/poltoraha/pivka/serviceImpl/ReaderServiceImpl.java index 7648630..0f24e30 100644 --- a/src/main/java/ttv/poltoraha/pivka/serviceImpl/ReaderServiceImpl.java +++ b/src/main/java/ttv/poltoraha/pivka/serviceImpl/ReaderServiceImpl.java @@ -9,6 +9,7 @@ import ttv.poltoraha.pivka.entity.Reader; import ttv.poltoraha.pivka.entity.Reading; import ttv.poltoraha.pivka.repository.BookRepository; +import ttv.poltoraha.pivka.repository.QuoteRepository; import ttv.poltoraha.pivka.repository.ReaderRepository; import ttv.poltoraha.pivka.service.ReaderService; import util.MyUtility; @@ -19,6 +20,8 @@ public class ReaderServiceImpl implements ReaderService { private final ReaderRepository readerRepository; private final BookRepository bookRepository; + private final QuoteRepository quoteRepository; + @Override public void createQuote(String username, Integer book_id, String text) { val newQuote = new Quote(); @@ -33,7 +36,8 @@ public void createQuote(String username, Integer book_id, String text) { reader.getQuotes().add(newQuote); // todo потенциально лучше сейвить quoteRepository. Чем меньше вложенностей у сохраняемой сущности - тем эффективнее это будет происходить. - readerRepository.save(reader); +// readerRepository.save(reader); + quoteRepository.save(newQuote); } @Override From 2c016f1d4a7abe2eb2d7ff70658e1d7f2d22e0f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB?= Date: Fri, 30 May 2025 02:01:07 +0300 Subject: [PATCH 2/4] (PIVO-103-hamm1ng) --- .../poltoraha/pivka/serviceImpl/RecommendationServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ttv/poltoraha/pivka/serviceImpl/RecommendationServiceImpl.java b/src/main/java/ttv/poltoraha/pivka/serviceImpl/RecommendationServiceImpl.java index b94366a..693f5f9 100644 --- a/src/main/java/ttv/poltoraha/pivka/serviceImpl/RecommendationServiceImpl.java +++ b/src/main/java/ttv/poltoraha/pivka/serviceImpl/RecommendationServiceImpl.java @@ -98,7 +98,7 @@ public List recommendBook(String username) { */ @Override public List recommendQuoteByBook(Integer book_id) { - if (bookRepository.existsById(book_id)) { + if (!bookRepository.existsById(book_id)) { throw new EntityNotFoundException(String.format("Entity book with id = %s was not found", book_id)); } From 9dc1ca2b74128bfde178dc478637f4231a168e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB?= Date: Fri, 30 May 2025 16:04:05 +0300 Subject: [PATCH 3/4] =?UTF-8?q?=D1=81=D0=BB=D0=BE=D0=B7=D0=B4=D0=B0=D0=BB?= =?UTF-8?q?=20=D1=81=D1=83=D1=89=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=B3?= =?UTF-8?q?=D0=BB=D0=B0=D0=B2=20=D0=B8=20=D1=81=D0=B2=D1=8F=D0=B7=D0=B0?= =?UTF-8?q?=D0=BB=20=D0=B5=C3=90=C3=90=D1=91=20=D1=81=20=D1=81=D1=83=D1=89?= =?UTF-8?q?=D0=BD=D0=BE=D1=81=D1=82=D1=8C=D1=8E=20=D0=BA=D0=BD=D0=B8=D0=B3?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ttv/poltoraha/pivka/entity/Chapter.java | 17 ++++++++++++++ .../pivka/repository/ChapterRepository.java | 7 ++++++ .../changelog/v1.0.4_add_chapter_tables.xml | 23 +++++++++++++++++++ src/main/resources/db/liquibase-master.xml | 1 + 4 files changed, 48 insertions(+) create mode 100644 src/main/java/ttv/poltoraha/pivka/entity/Chapter.java create mode 100644 src/main/java/ttv/poltoraha/pivka/repository/ChapterRepository.java create mode 100644 src/main/resources/db/changelog/v1.0.4_add_chapter_tables.xml diff --git a/src/main/java/ttv/poltoraha/pivka/entity/Chapter.java b/src/main/java/ttv/poltoraha/pivka/entity/Chapter.java new file mode 100644 index 0000000..f9a391c --- /dev/null +++ b/src/main/java/ttv/poltoraha/pivka/entity/Chapter.java @@ -0,0 +1,17 @@ +package ttv.poltoraha.pivka.entity; + +import jakarta.persistence.*; + +@Entity +@Table(name = "chapter") +public class Chapter { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + private String title; + private Integer pageNumber; + + @ManyToOne + @JoinColumn(name = "book_id") + private Book book; +} diff --git a/src/main/java/ttv/poltoraha/pivka/repository/ChapterRepository.java b/src/main/java/ttv/poltoraha/pivka/repository/ChapterRepository.java new file mode 100644 index 0000000..02b301e --- /dev/null +++ b/src/main/java/ttv/poltoraha/pivka/repository/ChapterRepository.java @@ -0,0 +1,7 @@ +package ttv.poltoraha.pivka.repository; + +import org.springframework.data.repository.CrudRepository; +import ttv.poltoraha.pivka.entity.Chapter; + +public interface ChapterRepository extends CrudRepository { +} diff --git a/src/main/resources/db/changelog/v1.0.4_add_chapter_tables.xml b/src/main/resources/db/changelog/v1.0.4_add_chapter_tables.xml new file mode 100644 index 0000000..bfe50f7 --- /dev/null +++ b/src/main/resources/db/changelog/v1.0.4_add_chapter_tables.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/db/liquibase-master.xml b/src/main/resources/db/liquibase-master.xml index 7c7b37c..bdf2bb1 100644 --- a/src/main/resources/db/liquibase-master.xml +++ b/src/main/resources/db/liquibase-master.xml @@ -7,6 +7,7 @@ + From 54f8c1a9259660c5990a0279d2a58850eb957856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB?= Date: Mon, 2 Jun 2025 18:51:53 +0300 Subject: [PATCH 4/4] pivo-106-hamming --- .../pivka/controller/AuthorController.java | 5 +- .../poltoraha/pivka/dao/dto/AuthorDto.java | 15 + .../ttv/poltoraha/pivka/entity/Author.java | 9 +- .../AuthorAlreadyExistsException.java | 7 + .../exception/AuthorNotFoundException.java | 7 + .../ttv/poltoraha/pivka/exception/index.html | 311 ++++++++++++++++++ .../poltoraha/pivka/mapping/AuthorMapper.java | 21 ++ .../pivka/repository/AuthorRepository.java | 3 + .../pivka/repository/ChapterRepository.java | 2 + .../pivka/service/AuthorService.java | 3 +- .../pivka/serviceImpl/AuthorServiceImpl.java | 41 ++- .../app/repository/AuthorRepositoryTest.java | 5 +- .../serviceImpl/AuthorServiceImplTest.java | 192 +++++++++++ .../pivka/app/util/DataAuthorUtils.java | 32 ++ 14 files changed, 624 insertions(+), 29 deletions(-) create mode 100644 src/main/java/ttv/poltoraha/pivka/dao/dto/AuthorDto.java create mode 100644 src/main/java/ttv/poltoraha/pivka/exception/AuthorAlreadyExistsException.java create mode 100644 src/main/java/ttv/poltoraha/pivka/exception/AuthorNotFoundException.java create mode 100644 src/main/java/ttv/poltoraha/pivka/exception/index.html create mode 100644 src/main/java/ttv/poltoraha/pivka/mapping/AuthorMapper.java create mode 100644 src/test/java/ttv/poltoraha/pivka/app/serviceImpl/AuthorServiceImplTest.java create mode 100644 src/test/java/ttv/poltoraha/pivka/app/util/DataAuthorUtils.java diff --git a/src/main/java/ttv/poltoraha/pivka/controller/AuthorController.java b/src/main/java/ttv/poltoraha/pivka/controller/AuthorController.java index 3811627..e47786b 100644 --- a/src/main/java/ttv/poltoraha/pivka/controller/AuthorController.java +++ b/src/main/java/ttv/poltoraha/pivka/controller/AuthorController.java @@ -2,6 +2,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; +import ttv.poltoraha.pivka.dao.dto.AuthorDto; import ttv.poltoraha.pivka.entity.Author; import ttv.poltoraha.pivka.entity.Book; import ttv.poltoraha.pivka.service.AuthorService; @@ -17,8 +18,8 @@ public class AuthorController { private final AuthorService authorService; @PostMapping("/create") - public void createAuthor(@RequestBody Author author) { - authorService.create(author); + public void createAuthor(@RequestBody AuthorDto authorDto) { + authorService.create(authorDto); } @PostMapping("/delete") diff --git a/src/main/java/ttv/poltoraha/pivka/dao/dto/AuthorDto.java b/src/main/java/ttv/poltoraha/pivka/dao/dto/AuthorDto.java new file mode 100644 index 0000000..3d6309f --- /dev/null +++ b/src/main/java/ttv/poltoraha/pivka/dao/dto/AuthorDto.java @@ -0,0 +1,15 @@ +package ttv.poltoraha.pivka.dao.dto; + +import lombok.Builder; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +@Data +@Builder +@Getter +@Setter +public class AuthorDto { + private String fullName; + private Double avgRating; +} diff --git a/src/main/java/ttv/poltoraha/pivka/entity/Author.java b/src/main/java/ttv/poltoraha/pivka/entity/Author.java index 70ef950..f19d5ad 100644 --- a/src/main/java/ttv/poltoraha/pivka/entity/Author.java +++ b/src/main/java/ttv/poltoraha/pivka/entity/Author.java @@ -1,15 +1,16 @@ package ttv.poltoraha.pivka.entity; import jakarta.persistence.*; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; +import lombok.*; import java.util.List; // Энтити - это привязка класса к конкретной табличке в БД -@Entity(name="author") @Data +@Entity(name="author") +@Builder @ToString +@AllArgsConstructor +@NoArgsConstructor public class Author { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/ttv/poltoraha/pivka/exception/AuthorAlreadyExistsException.java b/src/main/java/ttv/poltoraha/pivka/exception/AuthorAlreadyExistsException.java new file mode 100644 index 0000000..e352711 --- /dev/null +++ b/src/main/java/ttv/poltoraha/pivka/exception/AuthorAlreadyExistsException.java @@ -0,0 +1,7 @@ +package ttv.poltoraha.pivka.exception; + +public class AuthorAlreadyExistsException extends RuntimeException { + public AuthorAlreadyExistsException(String message) { + super(message); + } +} diff --git a/src/main/java/ttv/poltoraha/pivka/exception/AuthorNotFoundException.java b/src/main/java/ttv/poltoraha/pivka/exception/AuthorNotFoundException.java new file mode 100644 index 0000000..c42557b --- /dev/null +++ b/src/main/java/ttv/poltoraha/pivka/exception/AuthorNotFoundException.java @@ -0,0 +1,7 @@ +package ttv.poltoraha.pivka.exception; + +public class AuthorNotFoundException extends RuntimeException { + public AuthorNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/ttv/poltoraha/pivka/exception/index.html b/src/main/java/ttv/poltoraha/pivka/exception/index.html new file mode 100644 index 0000000..97d9103 --- /dev/null +++ b/src/main/java/ttv/poltoraha/pivka/exception/index.html @@ -0,0 +1,311 @@ + + + + + + ООО СК "МЕРЕДИАН" - Строительная компания + + + +
+ +
Строим будущее вместе
+
+ + + +
+

Профессиональное строительство под ключ

+

Качество, надежность и соблюдение сроков - наши главные принципы

+ Оставить заявку +
+ +
+

О компании

+

ООО СК "МЕРЕДИАН" - это современная строительная компания, которая уже более 10 лет успешно реализует проекты различной сложности. Наша команда профессионалов гарантирует качественное выполнение работ на всех этапах строительства.

+

Мы специализируемся на строительстве жилых и коммерческих объектов, реконструкции зданий и капитальном ремонте. Наши клиенты - это частные лица, крупные компании и государственные учреждения.

+
+ +
+

Наши услуги

+
+
+ Строительство домов +

Строительство домов

+

Полный цикл строительства частных домов и коттеджей под ключ с использованием современных материалов и технологий.

+
+
+ Коммерческое строительство +

Коммерческое строительство

+

Возведение офисных зданий, торговых центров, складов и других коммерческих объектов любой сложности.

+
+
+ Реконструкция и ремонт +

Реконструкция и ремонт

+

Качественная реконструкция зданий, капитальный и косметический ремонт помещений любого назначения.

+
+
+
+ +
+

Наши проекты

+ +
+ +
+

Свяжитесь с нами

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+ +
+ +

© 2023 ООО СК "МЕРЕДИАН". Все права защищены.

+
+ + diff --git a/src/main/java/ttv/poltoraha/pivka/mapping/AuthorMapper.java b/src/main/java/ttv/poltoraha/pivka/mapping/AuthorMapper.java new file mode 100644 index 0000000..b62f5eb --- /dev/null +++ b/src/main/java/ttv/poltoraha/pivka/mapping/AuthorMapper.java @@ -0,0 +1,21 @@ +package ttv.poltoraha.pivka.mapping; + +import ttv.poltoraha.pivka.dao.dto.AuthorDto; +import ttv.poltoraha.pivka.entity.Author; + +public class AuthorMapper { + + public Author mapFromDtoToEntity(AuthorDto dto) { + return Author.builder() + .avgRating(dto.getAvgRating()) + .fullName(dto.getFullName()) + .build(); + } + + public AuthorDto mapFromEntityToDto(Author entity) { + return AuthorDto.builder() + .avgRating(entity.getAvgRating()) + .fullName(entity.getFullName()) + .build(); + } +} diff --git a/src/main/java/ttv/poltoraha/pivka/repository/AuthorRepository.java b/src/main/java/ttv/poltoraha/pivka/repository/AuthorRepository.java index 87de14a..fa21989 100644 --- a/src/main/java/ttv/poltoraha/pivka/repository/AuthorRepository.java +++ b/src/main/java/ttv/poltoraha/pivka/repository/AuthorRepository.java @@ -8,6 +8,7 @@ import ttv.poltoraha.pivka.entity.Author; import java.util.List; +import java.util.Optional; // Репозиторий - это интерфейс, позволяющий нам писать изолированные от sql логики запросы в БД @Repository @@ -20,4 +21,6 @@ public interface AuthorRepository extends CrudRepository { @Query("SELECT DISTINCT a FROM author a JOIN a.books b WHERE b.tags LIKE %:tag% ORDER BY a.avgRating DESC, a.id ASC") List findTopAuthorsByTag(@Param("tag") String tag); + + public Optional findAuthorByFullName(String fullName); } diff --git a/src/main/java/ttv/poltoraha/pivka/repository/ChapterRepository.java b/src/main/java/ttv/poltoraha/pivka/repository/ChapterRepository.java index 02b301e..9f45e6a 100644 --- a/src/main/java/ttv/poltoraha/pivka/repository/ChapterRepository.java +++ b/src/main/java/ttv/poltoraha/pivka/repository/ChapterRepository.java @@ -1,7 +1,9 @@ package ttv.poltoraha.pivka.repository; import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; import ttv.poltoraha.pivka.entity.Chapter; +@Repository public interface ChapterRepository extends CrudRepository { } diff --git a/src/main/java/ttv/poltoraha/pivka/service/AuthorService.java b/src/main/java/ttv/poltoraha/pivka/service/AuthorService.java index 082afa2..6cfb677 100644 --- a/src/main/java/ttv/poltoraha/pivka/service/AuthorService.java +++ b/src/main/java/ttv/poltoraha/pivka/service/AuthorService.java @@ -1,5 +1,6 @@ package ttv.poltoraha.pivka.service; +import ttv.poltoraha.pivka.dao.dto.AuthorDto; import ttv.poltoraha.pivka.entity.Author; import ttv.poltoraha.pivka.entity.Book; @@ -7,7 +8,7 @@ // Сервис - это классы с основной логикой. Хорошей практикой является создание интерфейса. // Интерфейс позволяет нам удобно расширять функционал или делать несколько реализаций без изменения, например, вызова метода в контроллере public interface AuthorService { - public void create(Author author); + public void create(AuthorDto authorDto); public void delete(Integer id); public void addBook(Integer id, Book book); public void addBooks(Integer id, List books); diff --git a/src/main/java/ttv/poltoraha/pivka/serviceImpl/AuthorServiceImpl.java b/src/main/java/ttv/poltoraha/pivka/serviceImpl/AuthorServiceImpl.java index 5344bec..97cde03 100644 --- a/src/main/java/ttv/poltoraha/pivka/serviceImpl/AuthorServiceImpl.java +++ b/src/main/java/ttv/poltoraha/pivka/serviceImpl/AuthorServiceImpl.java @@ -1,13 +1,18 @@ package ttv.poltoraha.pivka.serviceImpl; +import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import lombok.val; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import ttv.poltoraha.pivka.dao.dto.AuthorDto; import ttv.poltoraha.pivka.entity.Author; import ttv.poltoraha.pivka.entity.Book; +import ttv.poltoraha.pivka.exception.AuthorAlreadyExistsException; +import ttv.poltoraha.pivka.exception.AuthorNotFoundException; +import ttv.poltoraha.pivka.mapping.AuthorMapper; import ttv.poltoraha.pivka.repository.AuthorRepository; import ttv.poltoraha.pivka.service.AuthorService; @@ -15,41 +20,47 @@ // Имплементации интерфейсов с бизнес-логикой @Service +@Transactional @RequiredArgsConstructor public class AuthorServiceImpl implements AuthorService { + private final AuthorRepository authorRepository; + private final AuthorMapper authorMapper; // todo как будто надо насрать всякими мапперами @Override - @Transactional - public void create(Author author) { - authorRepository.save(author); + public void create(AuthorDto authorDto) { + authorRepository.findAuthorByFullName(authorDto.getFullName()).ifPresent( + author -> { + throw new AuthorAlreadyExistsException(String.format( + "Author with fullName = %s already exists", authorDto.getFullName()) + ); + } + ); + + Author savedAuthor = authorMapper.mapFromDtoToEntity(authorDto); + authorRepository.save(savedAuthor); } @Override - @Transactional public void delete(Integer id) { - authorRepository.deleteById(id); + Author obtainedAuthor = getOrThrow(id); + authorRepository.deleteById(obtainedAuthor.getId()); } @Override - @Transactional public void addBooks(Integer id, List books) { val author = getOrThrow(id); - author.getBooks().addAll(books); } @Override - @Transactional public void addBook(Integer id, Book book) { val author = getOrThrow(id); - author.getBooks().add(book); } @Override - @Transactional public List getTopAuthorsByTag(String tag, int count) { Pageable pageable = PageRequest.of(0, count); val authors = authorRepository.findTopAuthorsByTag(tag); @@ -58,13 +69,7 @@ public List getTopAuthorsByTag(String tag, int count) { } private Author getOrThrow(Integer id) { - val optionalAuthor = authorRepository.findById(id); - val author = optionalAuthor.orElse(null); - - if (author == null) { - throw new RuntimeException("Author with id = " + id + " not found"); - } - - return author; + return authorRepository.findById(id) + .orElseThrow(() -> new AuthorNotFoundException("Author with id = " + id + " not found")); } } diff --git a/src/test/java/ttv/poltoraha/pivka/app/repository/AuthorRepositoryTest.java b/src/test/java/ttv/poltoraha/pivka/app/repository/AuthorRepositoryTest.java index 0bfdd39..c606ead 100644 --- a/src/test/java/ttv/poltoraha/pivka/app/repository/AuthorRepositoryTest.java +++ b/src/test/java/ttv/poltoraha/pivka/app/repository/AuthorRepositoryTest.java @@ -7,7 +7,4 @@ @SpringBootTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY) // Используйте H2 вместо реальной БД @Transactional // Обеспечивает откат транзакций после каждого теста -public class AuthorRepositoryTest { - - -} +public class AuthorRepositoryTest { } diff --git a/src/test/java/ttv/poltoraha/pivka/app/serviceImpl/AuthorServiceImplTest.java b/src/test/java/ttv/poltoraha/pivka/app/serviceImpl/AuthorServiceImplTest.java new file mode 100644 index 0000000..82dccd3 --- /dev/null +++ b/src/test/java/ttv/poltoraha/pivka/app/serviceImpl/AuthorServiceImplTest.java @@ -0,0 +1,192 @@ +package ttv.poltoraha.pivka.app.serviceImpl; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import ttv.poltoraha.pivka.app.util.DataAuthorUtils; +import ttv.poltoraha.pivka.dao.dto.AuthorDto; +import ttv.poltoraha.pivka.entity.Author; +import ttv.poltoraha.pivka.entity.Book; +import ttv.poltoraha.pivka.exception.AuthorAlreadyExistsException; +import ttv.poltoraha.pivka.exception.AuthorNotFoundException; +import ttv.poltoraha.pivka.mapping.AuthorMapper; +import ttv.poltoraha.pivka.repository.AuthorRepository; +import ttv.poltoraha.pivka.serviceImpl.AuthorServiceImpl; + +import java.util.*; + +import static org.assertj.core.api.Assertions.assertThat; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.*; + +//@SpringBootTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY) // Используйте H2 вместо реальной БД +@Transactional // Обеспечивает откат транзакций после каждого теста +@ExtendWith(MockitoExtension.class) +public class AuthorServiceImplTest { + + @Mock + private AuthorRepository authorRepository; + @Mock + private AuthorMapper authorMapper; + @InjectMocks + private AuthorServiceImpl serviceUnderTest; + + @Test + @DisplayName("Test create author functionality") + public void givenAuthorToCreate_whenCreateNewAuthor_thenRepositoryIsCalled() { + //given + AuthorDto authorToCreate = DataAuthorUtils.getLevTolstoyTransientDto(); + Author expectedAuthor = DataAuthorUtils.getLevTolstoyPersisted(); + + given(authorRepository.findAuthorByFullName(anyString())) + .willReturn(Optional.empty()); + + given(authorMapper.mapFromDtoToEntity(any(AuthorDto.class))) + .willReturn(expectedAuthor); + + given(authorRepository.save(any(Author.class))) + .willReturn(expectedAuthor); + //when + serviceUnderTest.create(authorToCreate); + //then + verify(authorRepository, times(1)).save(any(Author.class)); + } + + @Test + @DisplayName("Test create author with duplicate full name functionality") + public void givenAuthorToCreateWithDuplicateFullName_whenCreateNewAuthor_thenExceptionIsThrow() { + //given + AuthorDto authorToCreate = DataAuthorUtils.getLevTolstoyTransientDto(); + given(authorRepository.findAuthorByFullName(authorToCreate.getFullName())) + .willReturn(Optional.of(DataAuthorUtils.getLevTolstoyPersisted())); + //when + assertThrows( + AuthorAlreadyExistsException.class, () -> serviceUnderTest.create(authorToCreate) + ); + //then + verify(authorRepository, never()).save(any(Author.class)); + } + + @Test + @DisplayName("Test delete author by id functionality") + public void givenId_whenDeleteById_thenRepositoryIsCalled() { + //given + given(authorRepository.findById(anyInt())) + .willReturn(Optional.of(DataAuthorUtils.getLevTolstoyPersisted())); + //when + serviceUnderTest.delete(1); + //then + verify(authorRepository, times(1)).deleteById(anyInt()); + } + + @Test + @DisplayName("Test delete author by incorrect id functionality") + public void givenIncorrectId_whenDeleteById_thenExceptionIsThrow() { + //given + given(authorRepository.findById(anyInt())) + .willReturn(Optional.empty()); + //when + assertThrows( + AuthorNotFoundException.class, () -> serviceUnderTest.delete(1) + ); + //then + verify(authorRepository, never()).deleteById(anyInt()); + } + + @Test + @DisplayName("Test add book for author by id functionality") + public void givenBookForAuthorById_whenAddBookByAuthorId_thenBookAddedInListBook() { + // given + Author author = spy(Author.builder() + .id(1) + .fullName("Лев Толстой") + .books(new ArrayList<>()) + .build()); + + Book book = Book.builder() + .author(author) + .article("War and Peace") + .build(); + + given(authorRepository.findById(anyInt())) + .willReturn(Optional.of(author)); + + // when + serviceUnderTest.addBook(1, book); + + // then + assertThat(author.getBooks()) + .hasSize(1) + .containsExactly(book); + assertThat(book.getAuthor()).isEqualTo(author); + } + + @Test + @DisplayName("Test add book for author by incorrect id functionality") + public void givenBookForAuthorByIncorrectId_whenAddBookByIncorrectAuthorId_thenExceptionIsThrow() { + //given + //when + //then + } + + @Test + @DisplayName("Test add books for author by id functionality") + public void givenBooksForAuthorById_whenAddBooksByAuthorId_thenBooksAddedInListBooks() { + // given + Author author = spy(Author.builder() + .id(1) + .fullName("Лев Толстой") + .books(new ArrayList<>()) + .build()); + + Book book1 = Book.builder() + .author(author) + .article("War and Peace") + .build(); + + Book book2 = Book.builder() + .author(author) + .article("Spider man") + .build(); + + Book book3 = Book.builder() + .author(author) + .article("Clean code") + .build(); + + List books = new ArrayList<>(Arrays.asList(book1,book2, book3)); + + given(authorRepository.findById(anyInt())) + .willReturn(Optional.of(author)); + + // when + serviceUnderTest.addBooks(1, books); + + // then + assertThat(CollectionUtils.isEmpty(author.getBooks())).isFalse(); + assertThat(author.getBooks()).hasSize(3); + } + + @Test + @DisplayName("Test add books for author by incorrect id functionality") + public void givenBooksForAuthorByIncorrectId_whenAddBooksByIncorrectAuthorId_thenExceptionIsThrow() { + //given + //when + //then + } + + +} + diff --git a/src/test/java/ttv/poltoraha/pivka/app/util/DataAuthorUtils.java b/src/test/java/ttv/poltoraha/pivka/app/util/DataAuthorUtils.java new file mode 100644 index 0000000..148fc6d --- /dev/null +++ b/src/test/java/ttv/poltoraha/pivka/app/util/DataAuthorUtils.java @@ -0,0 +1,32 @@ +package ttv.poltoraha.pivka.app.util; + +import ttv.poltoraha.pivka.dao.dto.AuthorDto; +import ttv.poltoraha.pivka.entity.Author; + +public class DataAuthorUtils { + + public static Author getLevTolstoyTransient() { + return Author.builder() + .fullName("Lev Nilolaevich Tolctoy") + .avgRating(10.0) + .build(); + } + + public static AuthorDto getLevTolstoyTransientDto() { + return AuthorDto.builder() + .fullName("Lev Nilolaevich Tolctoy") + .avgRating(10.0) + .build(); + } + + + public static Author getLevTolstoyPersisted() { + return Author.builder() + .id(1) + .fullName("Lev Nilolaevich Tolctoy") + .avgRating(10.0) + .build(); + } + + +}