diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bd5f2d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea + +blog-parent.iml \ No newline at end of file diff --git a/README.md b/README.md index 81e616e..cb417cf 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,156 @@ -# Quer fazer parte da Superbid Webservices? +# blog -Obrigado por se interessar em fazer parte do nosso time! +This is a simple blog application that uses Spring Boot, H2, Spring Data JPA and Spring Rest. This application provides the services of creating, editing, searching and deleting a post. -Esse teste faz parte de uma das etapas do nosso processo de seleção e o objetivo é avaliarmos o seu nível de conhecimento nas tecnologias que usamos. Nesse teste você terá 2 tarefas: +## Requirements +* Maven +* Java 8 and up -* responder um questionário sobre tecnologia e metodologia de desenvolvimento -* construir uma aplicação simples para termos ideia de como é o seu código e o que você considera importante em um projeto +## Usage -O questionário e a especificação da aplicação estão logo abaixo. +Application is using Maven as a dependency management system, so all required libraries should be downloaded automatically. The project use Spring Boot and H2 running in in-memory mode is used as a database. All these services are configured automatically and no action from user is required to set them up. -**A sua entrega será feita através de um Pull Request nesse repositório**. Faça um fork do repositório, implemente o seu código, responda as questões no `README.md` e faça um pull request. Sinta-se a vontade para colocar quaisquer outras informações que você considere pertinente no `README`. +``` +$ git clone git://github.com/igotavares/blog.git +$ cd blog/ +$ mvn compile +$ cd blog-war/ +$ mvn spring-boot:run +``` -# Questionário +## Tests -* Você já trabalhou com Spring Boot? -* O que você conhece sobre micro-serviços? +Following the order of the commands below it will be possible to test the services obtaining the respective results + +Testing the create post service + +``` +curl -H "Content-Type: application/json" --request POST -d "{\"title\":\"Welcome\",\"description\":\"to the new world\",\"publicationDate\":\"2018-01-01 00:00:00\"}" localhost:8080/blog/posts +``` + +Result + +``` +{"id":1} +``` + +Testing post search service + +``` +curl localhost:8080/blog/posts +``` + +Result + +``` +[{"id":1,"title":"Welcome","description":"to the new world","publicationDate":"2018-01-01 00:00:00"}] +``` + +Testing the post search service by id + +``` +curl localhost:8080/blog/posts/1 +``` + +Result + +``` +{"id":1,"title":"Welcome","description":"to the new world","publicationDate":"2018-01-01 00:00:00"} +``` + +Testing the post change service + +``` +curl -H "Content-Type: application/json" --request PUT -d "{\"id\":1,\"title\":\"Title\",\"description\":\"Description\",\"publicationDate\":\"2019-01-01 00:00:00\"}" localhost:8080/blog/posts/1 +``` + +Testing the post delete service + +``` +curl --request DELETE localhost:8080/blog/posts/1 +``` + +### Postman + +``` +https://www.getpostman.com/collections/b3680b4f92c7ef093a57 +``` + +## Contributing +Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. + +Please make sure to update tests as appropriate. + +## Release History + +* 1.0-SNAPSHOT + * Work in progress + + +## Questionário + + +* Você já trabalhou com Spring Boot? + + Sim, a maorias dos projetos na empresa na qual estou trabalhando utiliza Spring Boot. + +* O que você conhece sobre micro-serviços? + + Tenho conhecimento sobre os conceitos, alguns padrões adotados na arquitetura de micro-serviços e tive a oportunidade de trabalhar em um processo de implantação de micro-serviços na empresa que trabalho. + + * Cite algumas vantagens e desvantagens de usar esse modelo arquitetural -* Qual a sua experiência na construção de APIs? -* Alguma vez já teve que construir uma API pública? -* Como você controla o acesso à API? -* Como você trata questões da evolução das APIs? -* Você acha válido fazer testes automatizados? -* Imagine que você precisa construir uma API que vai ter um grande número de acessos. Como você garante que ela terá um tempo de resposta acessível durante um pico de acesso? -* Você conhece ou já trabalhou com containers? -* E orquestradores tipo Kubernetes ou Docker Swarm? -* Fale um pouco sobre o processo de versionamento de aplicações. Conhece Git? -* Como você usa branches, tags, etc. Tem algo no Github ou Gitlab? -* Você conhece CI/CD? Já chegou a fazer algum pipeline de CI/CD completo? -* Você já trabalhou com SCRUM ou Kanban? -* Conte um pouco sobre como foi fazer parte de um time ágil, quais dificuldades tiveram e como conseguiram superar. + As vantagens são que as aplicações ficam mais flexíveis, escaláveis e com manutenção mais simples. + A desvantagem é o aumento da complexidade das aplicações. + +* Qual a sua experiência na construção de APIs? + + Foram poucas empresas na qual trabalhei que não tinham APIs. Então já criei APIs e dei manutenções em algumas APIs, -# Teste prático (prazo 3 dias) +* Alguma vez já teve que construir uma API pública? + + Não até o presente momento. + +* Como você controla o acesso à API? + + Utilizando Token. + +* Como você trata questões da evolução das APIs? + + A evolução de uma API deve ser algo meticulosamente analisado para poder mensurar os seus impactos nas soluções que elas afetam. Em alguns casos é recomendado trabalhar com um processo de versionamento dos serviços, caso a API contenha uma grande quantidade de consumidores. + +* Você acha válido fazer testes automatizados? + + Sim, trabalho no processo de implatação de testes automatizados na fase de desenvolvimento. Atuo como Coaching de testes. -Considerando a funcionalidade de um blog, construa uma API Rest contendo as operações de básicas de CRUD: inclusão, atualização, exclusão e consulta. O recurso em questão deve ter a seguinte estrutura: +* Imagine que você precisa construir uma API que vai ter um grande número de acessos. Como você garante que ela terá um tempo de resposta acessível durante um pico de acesso? + + Escalando a aplicação utilizando cluster e load balance. -* Post -* ID -* Data de Publicação -* Título -* Descrição +* Você conhece ou já trabalhou com containers? -## Tecnologias utilizadas + Tenho conhecimento. No momento não tenho experiência na utilização do docker em uma empresa, mas estou estudando, experimentando e adotando o docker nos projetos pessoais. -* Spring Boot -* Java 8 -* Hibernate -* Banco de dados em memória -* Postman Collection (para testes da api) +* E orquestradores tipo Kubernetes ou Docker Swarm? -## Critérios a serem avaliados: + Tenho conhecimento, mas não trabalhei com os orquestradores. + +* Fale um pouco sobre o processo de versionamento de aplicações. Conhece Git? + + Conheço o git. O versionamento de aplicações é extremamente importante no desenvolvimento de software pois ele armazena o seu código fonte mantendo o histórico. O versionamento facilita no desenvolvimento em equipes, pois a equipe consegue trabalhar em uma determinada versão do software sem impactar outras equipes ou uma derminada entrega. -* Qualidade de Código -* Cobertura de Testes -* Definição dos Serviços Rest -* Documentação -* Qualquer critério que vocês considere pertinente +* Como você usa branches, tags, etc. Tem algo no Github ou Gitlab? + Utilizo as branches da seguinte forma: utilizo a master como Tag e nela contém a última versão da aplicação que está em produção. Develop é a última versão da aplicação que está em desenvolvimento, as feature branches são criadas para conter as novas demandas, hotfixes são as branches criadas para correções de bug de produção e a release branches são criadas para manter o código que será disponibilizado em outro ambiente que não é o de produção. + Tenho projeto no Github e no Bitbucket. -Uma dica: pense no teste prático como sendo um algo real que você faria ou gostaria de fazer no seu trabalho, pois esse será o seu cartão de visitas. +* Você conhece CI/CD? Já chegou a fazer algum pipeline de CI/CD completo? + Conheço, a empresa na qual trabalho já tinha um processo CD. Na empresa atuei na arquitetura, desenvolvimento e implatação dos testes de aceitação seguindo a metodologia do BDD, configurei o jenkins e o servidor para a execução dos testes automatizados. Automatizei as execuções dos scripts de banco de dados nos ambientes. + Sim, já criei um pipeline completo. + +* Você já trabalhou com SCRUM ou Kanban? + Sim, trabalhei com os dois. + +* Conte um pouco sobre como foi fazer parte de um time ágil, quais dificuldades tiveram e como conseguiram superar. + Participei de um processo de implatação do Scrum na empresa que trabalho. Foram alguns meses de trabalho árduo para conseguir a unificação da equipe, manter os rituais do SCRUM e manter as scripts sem alteração de escopo. O Processo funcionou por alguns meses mas foi desfeito com a entrada da nova gerência. + + \ No newline at end of file diff --git a/blog-common/.gitignore b/blog-common/.gitignore new file mode 100644 index 0000000..f4cba02 --- /dev/null +++ b/blog-common/.gitignore @@ -0,0 +1,3 @@ +target + +blog-common.iml \ No newline at end of file diff --git a/blog-common/pom.xml b/blog-common/pom.xml new file mode 100644 index 0000000..7d156d0 --- /dev/null +++ b/blog-common/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + + blog-parent + com.ibeans + 1.0-SNAPSHOT + + + blog-common + + + + org.projectlombok + lombok + + + org.springframework + spring-web + + + com.fasterxml.jackson.core + jackson-annotations + + + + \ No newline at end of file diff --git a/blog-common/src/main/java/com/ibeans/blog/dta/PostDTO.java b/blog-common/src/main/java/com/ibeans/blog/dta/PostDTO.java new file mode 100644 index 0000000..0b5aa45 --- /dev/null +++ b/blog-common/src/main/java/com/ibeans/blog/dta/PostDTO.java @@ -0,0 +1,37 @@ +package com.ibeans.blog.dta; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * Created by igotavares on 07/08/2018. + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class PostDTO implements Serializable { + + private Long id; + + private String title; + + private String description; + + @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss") + private LocalDateTime publicationDate; + + public PostDTO(Long id) { + this(id, null, null, null); + } + + public PostDTO(String title, String description, LocalDateTime publicationDate) { + this(null, title, description, publicationDate); + } +} diff --git a/blog-common/src/main/java/com/ibeans/blog/exception/BlogException.java b/blog-common/src/main/java/com/ibeans/blog/exception/BlogException.java new file mode 100644 index 0000000..46554e4 --- /dev/null +++ b/blog-common/src/main/java/com/ibeans/blog/exception/BlogException.java @@ -0,0 +1,8 @@ +package com.ibeans.blog.exception; + +/** + * Created by igotavares on 07/08/2018. + */ +public class BlogException extends RuntimeException { + +} diff --git a/blog-common/src/main/java/com/ibeans/blog/exception/PostNotFoundException.java b/blog-common/src/main/java/com/ibeans/blog/exception/PostNotFoundException.java new file mode 100644 index 0000000..ab3c366 --- /dev/null +++ b/blog-common/src/main/java/com/ibeans/blog/exception/PostNotFoundException.java @@ -0,0 +1,11 @@ +package com.ibeans.blog.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * Created by igotavares on 07/08/2018. + */ +@ResponseStatus(value= HttpStatus.NOT_FOUND, reason="No such Post") +public class PostNotFoundException extends BlogException { +} diff --git a/blog-war/.gitignore b/blog-war/.gitignore new file mode 100644 index 0000000..cd43ca4 --- /dev/null +++ b/blog-war/.gitignore @@ -0,0 +1,3 @@ +target + +blog-war.iml \ No newline at end of file diff --git a/blog-war/Dockerfile b/blog-war/Dockerfile new file mode 100644 index 0000000..e142139 --- /dev/null +++ b/blog-war/Dockerfile @@ -0,0 +1,5 @@ +FROM openjdk:8-jdk-alpine +VOLUME /tmp +ARG JAR_FILE +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] \ No newline at end of file diff --git a/blog-war/pom.xml b/blog-war/pom.xml new file mode 100644 index 0000000..79a6404 --- /dev/null +++ b/blog-war/pom.xml @@ -0,0 +1,125 @@ + + + + blog-parent + com.ibeans + 1.0-SNAPSHOT + + 4.0.0 + + blog + blog-war + + + + io.springfox + springfox-swagger2 + + + io.springfox + springfox-swagger-ui + + + com.ibeans + blog-common + ${project.version} + + + net.sf.dozer + dozer + + + io.craftsman + dozer-jdk8-support + + + org.projectlombok + lombok + + + com.h2database + h2 + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-dependency-plugin + + + unpack + package + + unpack + + + + + ${project.groupId} + ${project.artifactId} + ${project.version} + + + + + + + + + + + + docker + + + + com.spotify + dockerfile-maven-plugin + 1.4.4 + + ibeans/blog + ${project.version} + + target/${project.build.finalName}.jar + + + + + default + install + + build + + + + + + + + + + + + \ No newline at end of file diff --git a/blog-war/src/main/java/com/ibeans/blog/BlogApplication.java b/blog-war/src/main/java/com/ibeans/blog/BlogApplication.java new file mode 100644 index 0000000..70224eb --- /dev/null +++ b/blog-war/src/main/java/com/ibeans/blog/BlogApplication.java @@ -0,0 +1,21 @@ +package com.ibeans.blog; + +import org.dozer.DozerBeanMapper; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +import java.util.Arrays; + +/** + * Created by igotavares on 07/08/2018. + */ +@SpringBootApplication +public class BlogApplication { + + public static void main(String[] args) { + SpringApplication.run(BlogApplication.class, args); + } + + +} diff --git a/blog-war/src/main/java/com/ibeans/blog/application/PostService.java b/blog-war/src/main/java/com/ibeans/blog/application/PostService.java new file mode 100644 index 0000000..eefe1f2 --- /dev/null +++ b/blog-war/src/main/java/com/ibeans/blog/application/PostService.java @@ -0,0 +1,22 @@ +package com.ibeans.blog.application; + + +import com.ibeans.blog.dta.PostDTO; + +import java.util.List; + +/** + * Created by igotavares on 07/08/2018. + */ +public interface PostService { + + PostDTO save(PostDTO post); + + List findAll(); + + PostDTO findBy(Long id); + + void delete(Long id); + + void update(PostDTO post, Long id); +} diff --git a/blog-war/src/main/java/com/ibeans/blog/application/impl/PostServiceImpl.java b/blog-war/src/main/java/com/ibeans/blog/application/impl/PostServiceImpl.java new file mode 100644 index 0000000..e4161e0 --- /dev/null +++ b/blog-war/src/main/java/com/ibeans/blog/application/impl/PostServiceImpl.java @@ -0,0 +1,83 @@ +package com.ibeans.blog.application.impl; + +import com.ibeans.blog.application.PostService; +import com.ibeans.blog.application.shared.Converter; +import com.ibeans.blog.domain.post.Post; +import com.ibeans.blog.domain.post.PostValidation; +import com.ibeans.blog.dta.PostDTO; +import com.ibeans.blog.exception.PostNotFoundException; +import com.ibeans.blog.infrastructure.jpa.PostRepository; +import org.dozer.Mapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Created by igotavares on 07/08/2018. + */ +@Service +public class PostServiceImpl implements PostService { + + private PostValidation validation; + private PostRepository repository; + private Converter converter; + + @Autowired + public PostServiceImpl(Mapper mapper, PostRepository repository, PostValidation validation) { + this.converter = new Converter(mapper, Post.class, PostDTO.class); + this.validation = validation; + this.repository = repository; + } + + @Override + public PostDTO save(PostDTO post) { + return Optional.of(post) + .map(converter::toEntity) + .map(repository::save) + .map(Post::getId) + .map(PostDTO::new) + .orElse(null); + } + + @Override + public void update(PostDTO post, Long id) { + validation.exists(id); + Optional.of(post) + .map(converter::toEntity) + .map(setId(id)) + .ifPresent(repository::save); + } + + private Function setId(Long id) { + return convertedPost -> { + convertedPost.setId(id); + return convertedPost; + }; + } + + @Override + public PostDTO findBy(Long id) { + return Optional.of(id) + .map(repository::findById) + .map(converter::toDTO) + .orElseThrow(PostNotFoundException::new); + } + + @Override + public List findAll() { + return repository.findAll().stream() + .map(converter::toDTO) + .collect(Collectors.toList()); + } + + @Override + public void delete(Long id) { + validation.exists(id); + Optional.of(id).ifPresent(repository::deleteById); + } + +} diff --git a/blog-war/src/main/java/com/ibeans/blog/application/shared/Converter.java b/blog-war/src/main/java/com/ibeans/blog/application/shared/Converter.java new file mode 100644 index 0000000..70d75f2 --- /dev/null +++ b/blog-war/src/main/java/com/ibeans/blog/application/shared/Converter.java @@ -0,0 +1,31 @@ +package com.ibeans.blog.application.shared; + +import lombok.RequiredArgsConstructor; +import org.dozer.Mapper; + +import java.util.Optional; + +/** + * Created by igotavares on 07/08/2018. + */ +@RequiredArgsConstructor +public class Converter { + + private final Mapper mapper; + private final Class entityClass; + private final Class dtoClass; + + public DTO toDTO(Optional entity) { + return entity.map(this::toDTO) + .orElse(null); + } + + public DTO toDTO(ENTITY entity) { + return mapper.map(entity, dtoClass); + } + + public ENTITY toEntity(DTO dto) { + return mapper.map(dto, entityClass); + } + +} diff --git a/blog-war/src/main/java/com/ibeans/blog/config/DozerConfig.java b/blog-war/src/main/java/com/ibeans/blog/config/DozerConfig.java new file mode 100644 index 0000000..aa4566b --- /dev/null +++ b/blog-war/src/main/java/com/ibeans/blog/config/DozerConfig.java @@ -0,0 +1,25 @@ +package com.ibeans.blog.config; + +import org.dozer.DozerBeanMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Arrays; + +/** + * Created by igotavares on 13/08/2018. + */ + +@Configuration +public class DozerConfig { + + private static final String DOZER_MAPPING_FILE = "dozerJdk8Converters.xml"; + + @Bean + public static DozerBeanMapper mapper() { + DozerBeanMapper mapper = new DozerBeanMapper(); + mapper.setMappingFiles(Arrays.asList(DOZER_MAPPING_FILE)); + return mapper; + } + +} diff --git a/blog-war/src/main/java/com/ibeans/blog/config/SwaggerConfig.java b/blog-war/src/main/java/com/ibeans/blog/config/SwaggerConfig.java new file mode 100644 index 0000000..2deae7d --- /dev/null +++ b/blog-war/src/main/java/com/ibeans/blog/config/SwaggerConfig.java @@ -0,0 +1,28 @@ +package com.ibeans.blog.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +/** + * Created by igotavares on 13/08/2018. + */ +@Configuration +@EnableSwagger2 +public class SwaggerConfig { + + @Bean + public Docket docket() { + return new Docket(DocumentationType.SWAGGER_2) + .select() + .apis(RequestHandlerSelectors.any()) + .paths(PathSelectors.any()) + .build(); + } + + +} diff --git a/blog-war/src/main/java/com/ibeans/blog/controller/PostController.java b/blog-war/src/main/java/com/ibeans/blog/controller/PostController.java new file mode 100644 index 0000000..a8a553b --- /dev/null +++ b/blog-war/src/main/java/com/ibeans/blog/controller/PostController.java @@ -0,0 +1,50 @@ +package com.ibeans.blog.controller; + +import com.ibeans.blog.application.PostService; +import com.ibeans.blog.dta.PostDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * Created by igotavares on 07/08/2018. + */ +@RestController +@RequestMapping(value = "/posts") +public class PostController { + + @Autowired + private PostService service; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public PostDTO save(@RequestBody PostDTO post) { + return service.save(post); + } + + @PutMapping(value = "/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void update(@RequestBody PostDTO post, @PathVariable Long id) { + service.update(post, id); + } + + @GetMapping + public List all() { + return service.findAll(); + } + + @GetMapping(value = "/{id}") + public PostDTO get(@PathVariable Long id) { + return service.findBy(id); + } + + @DeleteMapping(value = "/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void delete(@PathVariable Long id) { + service.delete(id); + } + + +} diff --git a/blog-war/src/main/java/com/ibeans/blog/domain/post/Post.java b/blog-war/src/main/java/com/ibeans/blog/domain/post/Post.java new file mode 100644 index 0000000..3db0a47 --- /dev/null +++ b/blog-war/src/main/java/com/ibeans/blog/domain/post/Post.java @@ -0,0 +1,41 @@ +package com.ibeans.blog.domain.post; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.validation.constraints.NotNull; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * Created by igotavares on 07/08/2018. + */ +@Entity +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Post implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @NotNull + private String title; + + @NotNull + private String description; + + @NotNull + private LocalDateTime publicationDate; + + public Post(String title, String description, LocalDateTime publicationDate) { + this(null, title, description, publicationDate); + } + +} diff --git a/blog-war/src/main/java/com/ibeans/blog/domain/post/PostSearch.java b/blog-war/src/main/java/com/ibeans/blog/domain/post/PostSearch.java new file mode 100644 index 0000000..175553e --- /dev/null +++ b/blog-war/src/main/java/com/ibeans/blog/domain/post/PostSearch.java @@ -0,0 +1,10 @@ +package com.ibeans.blog.domain.post; + +/** + * Created by igotavares on 07/08/2018. + */ +public interface PostSearch { + + boolean existsById(Long id); + +} diff --git a/blog-war/src/main/java/com/ibeans/blog/domain/post/PostValidation.java b/blog-war/src/main/java/com/ibeans/blog/domain/post/PostValidation.java new file mode 100644 index 0000000..059fb87 --- /dev/null +++ b/blog-war/src/main/java/com/ibeans/blog/domain/post/PostValidation.java @@ -0,0 +1,26 @@ +package com.ibeans.blog.domain.post; + +import com.ibeans.blog.exception.PostNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Created by igotavares on 07/08/2018. + */ +@Component +public class PostValidation { + + private final PostSearch search; + + @Autowired + public PostValidation(PostSearch search) { + this.search = search; + } + + public void exists(Long id) { + if (!search.existsById(id)) { + throw new PostNotFoundException(); + } + } + +} diff --git a/blog-war/src/main/java/com/ibeans/blog/infrastructure/jpa/PostRepository.java b/blog-war/src/main/java/com/ibeans/blog/infrastructure/jpa/PostRepository.java new file mode 100644 index 0000000..c6b03b9 --- /dev/null +++ b/blog-war/src/main/java/com/ibeans/blog/infrastructure/jpa/PostRepository.java @@ -0,0 +1,16 @@ +package com.ibeans.blog.infrastructure.jpa; + +import com.ibeans.blog.domain.post.Post; +import com.ibeans.blog.domain.post.PostSearch; +import org.springframework.data.repository.CrudRepository; + +import java.util.List; + +/** + * Created by igotavares on 07/08/2018. + */ +public interface PostRepository extends CrudRepository, PostSearch { + + List findAll(); + +} diff --git a/blog-war/src/main/resources/application.properties b/blog-war/src/main/resources/application.properties new file mode 100644 index 0000000..cec99f8 --- /dev/null +++ b/blog-war/src/main/resources/application.properties @@ -0,0 +1,2 @@ +server.servlet.context-path=/blog +server.port=8080 \ No newline at end of file diff --git a/blog-war/src/test/java/com/ibeans/blog/BlogConstants.java b/blog-war/src/test/java/com/ibeans/blog/BlogConstants.java new file mode 100644 index 0000000..186bd8a --- /dev/null +++ b/blog-war/src/test/java/com/ibeans/blog/BlogConstants.java @@ -0,0 +1,23 @@ +package com.ibeans.blog; + + +import java.time.LocalDateTime; + +/** + * Created by igotavares on 07/08/2018. + */ +public interface BlogConstants { + + interface PostConstants { + Long ID_IS_ONE = 1L; + String TITLE_IS_WELCOME = "Welcome"; + String DESCRIPTION_IS_TO_THE_NEW_WORLD = "to the new world"; + LocalDateTime PUBLICATION_DATE_IS_2018_01_01_00_00_00 = LocalDateTime.of(2018, 1, 1, 0, 0, 0); + + Long ID_IS_TWO = 2L; + String TITLE_IS_TITLE = "Title"; + String DESCRIPTION_IS_DESCRIPTPION = "Description"; + LocalDateTime PUBLICATION_DATE_IS_2017_02_03_10_05_03 = LocalDateTime.of(2017, 2, 3, 10,5,3); + } + +} diff --git a/blog-war/src/test/java/com/ibeans/blog/application/impl/PostServiceImplTest.java b/blog-war/src/test/java/com/ibeans/blog/application/impl/PostServiceImplTest.java new file mode 100644 index 0000000..f035ed8 --- /dev/null +++ b/blog-war/src/test/java/com/ibeans/blog/application/impl/PostServiceImplTest.java @@ -0,0 +1,191 @@ +package com.ibeans.blog.application.impl; + +import com.ibeans.blog.BlogConstants.PostConstants; +import com.ibeans.blog.config.DozerConfig; +import com.ibeans.blog.domain.post.Post; +import com.ibeans.blog.domain.post.PostValidation; +import com.ibeans.blog.dta.PostDTO; +import com.ibeans.blog.infrastructure.jpa.PostRepository; +import org.dozer.Mapper; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.List; +import java.util.Optional; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.verify; + +/** + * Created by igotavares on 07/08/2018. + */ +@RunWith(MockitoJUnitRunner.class) +public class PostServiceImplTest { + + @Mock + private PostRepository repositoryMock; + + @Mock + private PostValidation validationMock; + + private Mapper mapper; + + private PostServiceImpl postService; + + @Before + public void context() { + mapper = DozerConfig.mapper(); + postService = new PostServiceImpl(mapper, repositoryMock, validationMock); + } + + @Test + public void shouldSavePost() { + PostDTO post = new PostDTO(PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00); + + postService.save(post); + + Post convertedPost = new Post(PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00); + + verify(repositoryMock).save(convertedPost); + } + + @Test + public void givenASavedPost_shouldReturnPostWithId() { + Post savedPost = new Post( + PostConstants.ID_IS_ONE, + PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00); + + given(repositoryMock.save(any())).willReturn(savedPost); + + PostDTO post = new PostDTO(PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00); + + PostDTO actual = postService.save(post); + + PostDTO expected = new PostDTO(PostConstants.ID_IS_ONE); + + assertEquals(expected, actual); + } + + @Test + public void shouldValidatePostToChangeIt() { + PostDTO post = new PostDTO( + PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00); + + postService.update(post, PostConstants.ID_IS_ONE); + + verify(validationMock).exists(PostConstants.ID_IS_ONE); + } + + @Test + public void shouldUpdate() { + PostDTO post = new PostDTO( + PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00); + + postService.update(post, PostConstants.ID_IS_ONE); + + Post postToSave = new Post( + PostConstants.ID_IS_ONE, + PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00); + + verify(repositoryMock).save(postToSave); + } + + @Test + public void shouldFindById() { + Optional post = Optional.of(new Post( + PostConstants.ID_IS_ONE, + PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00)); + + given(repositoryMock.findById(any())).willReturn(post); + + postService.findBy(PostConstants.ID_IS_ONE); + + verify(repositoryMock).findById(PostConstants.ID_IS_ONE); + } + + @Test + public void givenFoundPost_thenShouldReturn() { + Optional post = Optional.of(new Post( + PostConstants.ID_IS_ONE, + PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00)); + + given(repositoryMock.findById(any())).willReturn(post); + + PostDTO actual = postService.findBy(PostConstants.ID_IS_ONE); + + PostDTO expected = new PostDTO( + PostConstants.ID_IS_ONE, + PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00); + + assertEquals(expected, actual); + } + + @Test + public void shouldFindAll() { + postService.findAll(); + + verify(repositoryMock).findAll(); + } + + @Test + public void givenFoundPosts_thenShouldReturnThem() { + Post post = new Post( + PostConstants.ID_IS_ONE, + PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00); + + given(repositoryMock.findAll()).willReturn(asList(post)); + + List actual = postService.findAll(); + + List expected = asList(new PostDTO( + PostConstants.ID_IS_ONE, + PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00)); + + assertEquals(expected, actual); + } + + @Test + public void shouldValidatePostToSaveIt() { + postService.delete(PostConstants.ID_IS_ONE); + + verify(validationMock).exists(PostConstants.ID_IS_ONE); + } + + @Test + public void shouldDeletePost() { + postService.delete(PostConstants.ID_IS_ONE); + + verify(repositoryMock).deleteById(PostConstants.ID_IS_ONE); + } + +} \ No newline at end of file diff --git a/blog-war/src/test/java/com/ibeans/blog/application/shared/ConverterTest.java b/blog-war/src/test/java/com/ibeans/blog/application/shared/ConverterTest.java new file mode 100644 index 0000000..38e2884 --- /dev/null +++ b/blog-war/src/test/java/com/ibeans/blog/application/shared/ConverterTest.java @@ -0,0 +1,62 @@ +package com.ibeans.blog.application.shared; + +import com.ibeans.blog.BlogConstants.PostConstants; +import com.ibeans.blog.config.DozerConfig; +import com.ibeans.blog.domain.post.Post; +import com.ibeans.blog.dta.PostDTO; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Created by igotavares on 07/08/2018. + */ +public class ConverterTest { + + private Converter converter; + + @Before + public void context() { + this.converter = new Converter<>(DozerConfig.mapper(), Post.class, PostDTO.class); + } + + @Test + public void shouldConverteEntityToDTO() throws Exception { + Post postToBeConverted = new Post( + PostConstants.ID_IS_ONE, + PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00); + + PostDTO actual = converter.toDTO(postToBeConverted); + + PostDTO expected = new PostDTO( + PostConstants.ID_IS_ONE, + PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00); + + assertEquals(expected, actual); + } + + @Test + public void shouldConverteDTOToEntity() throws Exception { + PostDTO postToBeConverted = new PostDTO( + PostConstants.ID_IS_ONE, + PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00); + + Post actual = converter.toEntity(postToBeConverted); + + Post expected = new Post( + PostConstants.ID_IS_ONE, + PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00); + + assertEquals(expected, actual); + } + +} \ No newline at end of file diff --git a/blog-war/src/test/java/com/ibeans/blog/controller/PostControllerTest.java b/blog-war/src/test/java/com/ibeans/blog/controller/PostControllerTest.java new file mode 100644 index 0000000..001bb5e --- /dev/null +++ b/blog-war/src/test/java/com/ibeans/blog/controller/PostControllerTest.java @@ -0,0 +1,212 @@ +package com.ibeans.blog.controller; + +import com.ibeans.blog.BlogConstants.PostConstants; +import com.ibeans.blog.application.PostService; +import com.ibeans.blog.dta.PostDTO; +import com.ibeans.blog.exception.PostNotFoundException; +import org.assertj.core.util.Lists; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Created by igotavares on 07/08/2018. + */ +@RunWith(SpringRunner.class) +@WebMvcTest(PostController.class) +public class PostControllerTest { + + private static final int EMPTY = 0; + private static final PostDTO POST_IS_NULL = null; + + @Autowired + private MockMvc mvcMock; + + @MockBean + private PostService postServiceMock; + + @Test + public void shouldSaveAPost() throws Exception { + String contet = "{\"title\":\"Welcome\",\"description\":\"to the new world\",\"publicationDate\":\"2018-01-01 00:00:00\"}"; + + mvcMock.perform(post("/posts") + .content(contet) + .contentType(MediaType.APPLICATION_JSON_UTF8)); + + PostDTO post = new PostDTO(PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00); + + verify(postServiceMock).save(post); + } + + @Test + public void givenASavedPost_thenShouldSeeThePostId() throws Exception { + PostDTO post = new PostDTO(PostConstants.ID_IS_ONE); + + given(postServiceMock.save(any())).willReturn(post); + + String contet = "{\"title\":\"Welcome\",\"description\":\"to the new world\",\"publicationDate\":\"2018-01-01 00:00:00\"}"; + + mvcMock.perform(post("/posts") + .content(contet) + .contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("id", is(PostConstants.ID_IS_ONE.intValue()))); + + } + + @Test + public void givenIdIsOne_thenShouldFindPostById() throws Exception { + mvcMock.perform(get("/posts/{id}", PostConstants.ID_IS_ONE)); + + verify(postServiceMock).findBy(PostConstants.ID_IS_ONE); + } + + @Test + public void givenAPostFound_thenShouldSeeThePost() throws Exception { + PostDTO post = new PostDTO(PostConstants.ID_IS_ONE, + PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00); + + given(postServiceMock.findBy(PostConstants.ID_IS_ONE)).willReturn(post); + + mvcMock.perform(get("/posts/{id}", PostConstants.ID_IS_ONE)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(jsonPath("id", is(PostConstants.ID_IS_ONE.intValue()))) + .andExpect(jsonPath("title", is(PostConstants.TITLE_IS_WELCOME))) + .andExpect(jsonPath("description", is(PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD))) + .andExpect(jsonPath("publicationDate", is(toString(PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00)))); + } + + @Test + public void givenDoNotHavePost_thenShouldSeePostNotFound() throws Exception { + given(postServiceMock.findBy(PostConstants.ID_IS_ONE)).willThrow(new PostNotFoundException()); + + mvcMock.perform(get("/posts/{id}", PostConstants.ID_IS_ONE)) + .andExpect(status().isNotFound()) + .andExpect(status().reason("No such Post")); + } + + @Test + public void shouldSearchAllPost() throws Exception { + mvcMock.perform(get("/posts")); + + verify(postServiceMock).findAll(); + } + + @Test + public void givenDoNotHavePost_thenItShouldGetEmptyList() throws Exception { + given(postServiceMock.findAll()).willReturn(empty()); + + mvcMock.perform(get("/posts").contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(jsonPath("$", hasSize(0))); + } + + @Test + public void givenFoundAPost_shouldSeePost() throws Exception { + PostDTO post = new PostDTO( + PostConstants.ID_IS_ONE, + PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00); + + given(postServiceMock.findAll()).willReturn(Lists.newArrayList(post)); + + mvcMock.perform(get("/posts/").contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].id", is(PostConstants.ID_IS_ONE.intValue()))) + .andExpect(jsonPath("$[0].title", is(PostConstants.TITLE_IS_WELCOME))) + .andExpect(jsonPath("$[0].description", is(PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD))) + .andExpect(jsonPath("$[0].publicationDate", is(toString(PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00)))); + } + + @Test + public void givenFoundTwoPost_shouldSeePosts() throws Exception { + PostDTO post = new PostDTO( + PostConstants.ID_IS_ONE, + PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00); + + PostDTO postTwo = new PostDTO( + PostConstants.ID_IS_TWO, + PostConstants.TITLE_IS_TITLE, + PostConstants.DESCRIPTION_IS_DESCRIPTPION, + PostConstants.PUBLICATION_DATE_IS_2017_02_03_10_05_03); + + given(postServiceMock.findAll()).willReturn(Lists.newArrayList(post, postTwo)); + + mvcMock.perform(get("/posts/").contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect(jsonPath("$[0].id", is(PostConstants.ID_IS_ONE.intValue()))) + .andExpect(jsonPath("$[0].title", is(PostConstants.TITLE_IS_WELCOME))) + .andExpect(jsonPath("$[0].description", is(PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD))) + .andExpect(jsonPath("$[0].publicationDate", is(toString(PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00)))) + .andExpect(jsonPath("$[1].id", is(PostConstants.ID_IS_TWO.intValue()))) + .andExpect(jsonPath("$[1].title", is(PostConstants.TITLE_IS_TITLE))) + .andExpect(jsonPath("$[1].description", is(PostConstants.DESCRIPTION_IS_DESCRIPTPION))) + .andExpect(jsonPath("$[1].publicationDate", is(toString(PostConstants.PUBLICATION_DATE_IS_2017_02_03_10_05_03)))); + } + + @Test + public void shouldUpdateThePost() throws Exception { + String contet = "{\"title\":\"Welcome\",\"description\":\"to the new world\",\"publicationDate\":\"2018-01-01 00:00:00\"}"; + + mvcMock.perform(put("/posts/{id}", PostConstants.ID_IS_ONE) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(contet)) + .andExpect(status().isNoContent()); + + PostDTO post = new PostDTO(PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00); + + verify(postServiceMock).update(post , PostConstants.ID_IS_ONE); + } + + @Test + public void givenIdIsOne_thenShouldDeletePost() throws Exception { + mvcMock.perform(delete("/posts/{id}", PostConstants.ID_IS_ONE)) + .andExpect(status().isNoContent()); + + verify(postServiceMock).delete(PostConstants.ID_IS_ONE); + } + + private List empty() { + return Lists.emptyList(); + } + + private String toString(LocalDateTime value) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + return value.format(formatter); + } + + + +} \ No newline at end of file diff --git a/blog-war/src/test/java/com/ibeans/blog/domain/post/PostValidationTest.java b/blog-war/src/test/java/com/ibeans/blog/domain/post/PostValidationTest.java new file mode 100644 index 0000000..19fd935 --- /dev/null +++ b/blog-war/src/test/java/com/ibeans/blog/domain/post/PostValidationTest.java @@ -0,0 +1,54 @@ +package com.ibeans.blog.domain.post; + +import com.ibeans.blog.BlogConstants.PostConstants; +import com.ibeans.blog.exception.PostNotFoundException; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +/** + * Created by igotavares on 08/08/2018. + */ +@RunWith(MockitoJUnitRunner.class) +public class PostValidationTest { + + @Mock + private PostSearch searchMock; + + @InjectMocks + private PostValidation validation; + + @Before + public void context() { + given(searchMock.existsById(any())).willReturn(Boolean.TRUE); + } + + @Test + public void givenIdIsOne_thenShouldCheckIfThereIsAPostById() throws Exception { + validation.exists(PostConstants.ID_IS_ONE); + + verify(searchMock).existsById(PostConstants.ID_IS_ONE); + } + + @Test(expected = PostNotFoundException.class) + public void givenThereIsNoPost_thenShouldThrowPostNotFound() throws Exception { + given(searchMock.existsById(any())).willReturn(Boolean.FALSE); + + validation.exists(PostConstants.ID_IS_ONE); + } + + @Test + public void givenFoundAPost_thenShouldNotThrowAnException() throws Exception { + given(searchMock.existsById(any())).willReturn(Boolean.TRUE); + + validation.exists(PostConstants.ID_IS_ONE); + } + +} \ No newline at end of file diff --git a/blog-war/src/test/java/com/ibeans/blog/infrastructure/jpa/PostRepositoryTest.java b/blog-war/src/test/java/com/ibeans/blog/infrastructure/jpa/PostRepositoryTest.java new file mode 100644 index 0000000..3b24d2e --- /dev/null +++ b/blog-war/src/test/java/com/ibeans/blog/infrastructure/jpa/PostRepositoryTest.java @@ -0,0 +1,115 @@ +package com.ibeans.blog.infrastructure.jpa; + +import com.ibeans.blog.BlogApplication; +import com.ibeans.blog.BlogConstants.PostConstants; +import com.ibeans.blog.domain.post.Post; +import org.hamcrest.Matcher; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +/** + * Created by igotavares on 07/08/2018. + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = BlogApplication.class) +public class PostRepositoryTest { + + @Autowired + private PostRepository repository; + + @Before + public void setUp() { + repository.deleteAll(); + } + + @Test + public void shouldFindPost() { + Post post = new Post(PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00); + + Post savedPost = repository.save(post); + + Optional postFound = repository.findById(savedPost.getId()); + + assertThat(postFound.orElse(null), create(PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00)); + } + + @Test + public void shouldFindAll() { + Post post = new Post(PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00); + + repository.save(post); + + List posts = repository.findAll(); + + assertThat(posts, contains(create(PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00))); + } + + @Test + public void shouldUpdate() { + Post post = new Post( + PostConstants.TITLE_IS_TITLE, + PostConstants.DESCRIPTION_IS_DESCRIPTPION, + PostConstants.PUBLICATION_DATE_IS_2017_02_03_10_05_03); + + post = repository.save(post); + + post = new Post( + post.getId(), + PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00); + + repository.save(post); + + List posts = repository.findAll(); + + assertThat(posts, contains(create(PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00))); + } + + + @Test + public void shouldDelete() { + Post post = new Post(PostConstants.TITLE_IS_WELCOME, + PostConstants.DESCRIPTION_IS_TO_THE_NEW_WORLD, + PostConstants.PUBLICATION_DATE_IS_2018_01_01_00_00_00); + + post = repository.save(post); + + repository.deleteById(post.getId()); + + List posts = repository.findAll(); + + assertThat(posts, empty()); + } + + private Matcher create(String title, String description, LocalDateTime publicationDate) { + return allOf(hasProperty("id", notNullValue()), + hasProperty("title", is(title)), + hasProperty("description", is(description)), + hasProperty("publicationDate", is(publicationDate)) + ); + } + +} \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..44f7607 --- /dev/null +++ b/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + + blog-war + blog-common + + + + org.springframework.boot + spring-boot-starter-parent + 2.0.3.RELEASE + + + com.ibeans + blog-parent + 1.0-SNAPSHOT + + pom + + + UTF-8 + UTF-8 + 1.8 + 5.5.1 + 2.7.0 + 1.0.2 + 1.16.16 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + + + + io.springfox + springfox-swagger2 + ${springfox-swagger2.version} + + + io.springfox + springfox-swagger-ui + ${springfox-swagger2.version} + + + net.sf.dozer + dozer + ${dozer.version} + + + io.craftsman + dozer-jdk8-support + ${dozer-jdk8-support.version} + + + org.projectlombok + lombok + ${lombok.version} + provided + + + + + \ No newline at end of file