├── .circleci
└── config.yml
├── .gitignore
├── LICENSE
├── README.md
├── pom.xml
└── src
├── integration-test
├── java
│ └── com
│ │ └── edwise
│ │ └── completespring
│ │ ├── controllers
│ │ ├── ITActuatorEndpointsTest.java
│ │ ├── ITBookControllerTest.java
│ │ └── ITFooControllerTest.java
│ │ ├── testswagger
│ │ └── ITCommonSwaggerAPITest.java
│ │ └── testutil
│ │ ├── IntegrationTestUtil.java
│ │ └── IsValidFormatDateYMDMatcher.java
└── resources
│ └── logback.xml
├── main
├── java
│ └── com
│ │ └── edwise
│ │ └── completespring
│ │ ├── Application.java
│ │ ├── ApplicationForServer.java
│ │ ├── assemblers
│ │ ├── BookResource.java
│ │ ├── BookResourceAssembler.java
│ │ ├── FooResource.java
│ │ └── FooResourceAssembler.java
│ │ ├── config
│ │ ├── SpringSecurityAuthenticationConfig.java
│ │ ├── SpringWebSecurityConfig.java
│ │ └── SwaggerConfig.java
│ │ ├── controllers
│ │ ├── BookController.java
│ │ ├── FooController.java
│ │ └── RestExceptionProcessor.java
│ │ ├── dbutils
│ │ └── DataLoader.java
│ │ ├── entities
│ │ ├── Author.java
│ │ ├── Book.java
│ │ ├── Foo.java
│ │ ├── Publisher.java
│ │ ├── SequenceId.java
│ │ ├── UserAccount.java
│ │ └── UserAccountType.java
│ │ ├── exceptions
│ │ ├── InvalidRequestException.java
│ │ ├── NotFoundException.java
│ │ ├── SequenceException.java
│ │ └── helpers
│ │ │ ├── ErrorInfo.java
│ │ │ └── ErrorItem.java
│ │ ├── repositories
│ │ ├── BookRepository.java
│ │ ├── SequenceIdRepository.java
│ │ ├── UserAccountRepository.java
│ │ └── impl
│ │ │ └── SequenceIdRepositoryImpl.java
│ │ └── services
│ │ ├── BookService.java
│ │ ├── Service.java
│ │ └── impl
│ │ └── BookServiceImpl.java
└── resources
│ ├── application.properties
│ └── logback.xml
└── test
├── java
└── com
│ └── edwise
│ └── completespring
│ ├── ApplicationForServerTest.java
│ ├── ApplicationTest.java
│ ├── assemblers
│ ├── BookResourceAssemblerTest.java
│ └── FooResourceAssemblerTest.java
│ ├── controllers
│ ├── BookControllerTest.java
│ ├── FooControllerTest.java
│ └── RestExceptionProcessorTest.java
│ ├── dbutils
│ └── DataLoaderTest.java
│ ├── entities
│ ├── AuthorTest.java
│ ├── BookTest.java
│ ├── FooTest.java
│ ├── PublisherTest.java
│ └── UserAccountTest.java
│ ├── exceptions
│ └── helpers
│ │ ├── ErrorInfoTest.java
│ │ └── ErrorItemTest.java
│ ├── repositories
│ └── impl
│ │ └── SequenceIdRepositoryImplTest.java
│ ├── services
│ └── BookServiceTest.java
│ └── testutil
│ └── BookBuilder.java
└── resources
└── logback.xml
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | # Java Maven CircleCI 2.0 configuration file
2 | #
3 | # Check https://circleci.com/docs/2.0/language-java/ for more details
4 | #
5 | version: 2
6 | jobs:
7 | build:
8 | docker:
9 | # specify the version you desire here
10 | - image: cimg/openjdk:21.0
11 |
12 | # Specify service dependencies here if necessary
13 | # CircleCI maintains a library of pre-built images
14 | # documented at https://circleci.com/docs/2.0/circleci-images/
15 | # - image: circleci/postgres:9.4
16 |
17 | working_directory: ~/complete-spring-project
18 |
19 | environment:
20 | # Customize the JVM maximum heap limit
21 | MAVEN_OPTS: -Xmx3200m
22 |
23 | steps:
24 | - checkout
25 |
26 | # Download and cache dependencies
27 | - restore_cache:
28 | keys:
29 | - v1-dependencies-{{ checksum "pom.xml" }}
30 | # fallback to using the latest cache if no exact match is found
31 | - v1-dependencies-
32 |
33 | - run: mvn dependency:go-offline
34 |
35 | - save_cache:
36 | paths:
37 | - ~/.m2
38 | key: v1-dependencies-{{ checksum "pom.xml" }}
39 |
40 | # run complete build!
41 | - run: mvn install
42 |
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | *.iml
3 | target
4 | logs
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://circleci.com/gh/edwise/complete-spring-project)
2 |
3 | Proyecto montado con Spring Boot y Java 21, con los siguientes frameworks / libraries / funcionalidades:
4 |
5 | - Spring Boot: versión 2. posibilidad de arrancar directamente con el plugin de maven o generar un war para despliegue en
6 | tomcat o similar. Con 'actuator' activado.
7 |
8 | - Servicio completo RESTful con Spring 5 (Books)
9 |
10 | - Uso de HATEOAS en el servicio
11 |
12 | - Documentado servicio con OpenAPI
13 |
14 | - Capa de base de datos con Spring DATA mongoDB
15 |
16 | - Jacoco para la cobertura de tests (plugin para maven)
17 |
18 | - Lombok para evitar código 'boilerplate'
19 |
20 | - Spring Exception Handling en los controllers
21 |
22 | - Validaciones en los entities, y envío de errores
23 |
24 | - Test de integración completos (mockeando el service)
25 |
26 | - Logs, con slfj4/logback configurados para escribir a fichero
27 |
28 | - Spring Security activo, con autenticación básica
29 |
30 | - Añadidas las developer tools de Spring Boot
31 |
32 | - ~~Uso de flapdoodle para simular la base de datos en los tests de integración~~ (Pendiente meterlo)
33 |
34 |
35 | Requisitos:
36 |
37 | - Maven (instalado y configurado)
38 |
39 | - mongoDB server (instalado y arrancado, en localhost y con el puerto por defecto)
40 |
41 |
42 | Comandos
43 |
44 | - Arrancar directamente con el plugin de SpringBoot:
45 |
46 | ```
47 | mvn spring-boot:run
48 | ```
49 |
50 |
51 | - Generar war, ejecutando test unitarios (así como generar informes de jacoco):
52 |
53 | ```
54 | mvn clean package
55 | ```
56 |
57 |
58 | - Generar war, ejecutando solo test de integración:
59 |
60 | ```
61 | mvn clean verify -P integration-test
62 | ```
63 |
64 | Usuarios de acceso:
65 |
66 | - Usuario para acceso a servicios rest (/api/*) -> user1 : password1
67 |
68 | - Usuario para acceso a administración (/actuator/*) -> admin : password1234
69 |
70 |
71 | Urls de acceso:
72 |
73 | - Swagger check -> http://localhost:8080/v2/api-docs?group=books-api
74 |
75 | - Swagger UI -> http://localhost:8080/swagger-ui.html
76 |
77 | - REST Books -> http://localhost:8080/api/books/
78 |
79 | - Jacoco -> DIRECTORIO_PROYECTO/target/sites/jacoco/index.html
80 |
81 | - Logs -> DIRECTORIO_PROYECTO/logs/csp*.log
82 |
83 | - Spring Boot actuator endpoints:
84 |
85 | http://localhost:8080/actuator/health
86 |
87 | http://localhost:8080/actuator/info
88 |
89 | ...
90 |
91 | Fuentes:
92 |
93 | - http://www.spring.io/
94 |
95 | - http://www.mkyong.com/mongodb/spring-data-mongodb-auto-sequence-id-example/
96 |
97 | - http://springfox.io/
98 |
99 | - http://www.petrikainulainen.net/programming/spring-framework/integration-testing-of-spring-mvc-applications-write-clean-assertions-with-jsonpath/
100 |
101 | - http://www.petrikainulainen.net/programming/maven/integration-testing-with-maven/
102 |
103 | - http://heidloff.net/article/usage-of-swagger-2-0-in-spring-boot-applications-to-document-apis/
104 |
105 | - http://docs.spring.io/spring-security/site/docs/4.0.x/reference/html/test-mockmvc.html
106 |
107 | - https://github.com/fakemongo/fongo
108 |
109 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | com.edwise.completespring
6 | completeSpringProject
7 | 2.0.0
8 | war
9 |
10 | completeSpringProject
11 | https://github.com/edwise/complete-spring-project
12 |
13 |
14 | org.springframework.boot
15 | spring-boot-starter-parent
16 | 3.4.2
17 |
18 |
19 |
20 |
21 |
22 | dev
23 |
24 |
25 | integration-test
26 |
27 | integration-test
28 | false
29 | true
30 |
31 |
32 |
33 |
34 |
35 | UTF-8
36 | 21
37 | ${project.basedir}/target/jacoco.exec
38 | ${project.basedir}/target/jacoco-it.exec
39 |
40 | 1.18.36
41 | 0.8.12
42 | 3.17.0
43 | 8.0.2.Final
44 | 2.9.0
45 |
46 | dev
47 | true
48 | false
49 |
50 |
51 |
52 |
53 |
54 |
55 | org.springframework.boot
56 | spring-boot-starter-web
57 |
58 |
59 |
60 | org.springframework.boot
61 | spring-boot-starter-tomcat
62 |
63 |
64 |
65 | org.springframework.boot
66 | spring-boot-starter-actuator
67 |
68 |
69 |
70 | org.springframework.boot
71 | spring-boot-starter-security
72 |
73 |
74 | org.springframework.security
75 | spring-security-test
76 | test
77 |
78 |
79 |
80 | org.springframework.boot
81 | spring-boot-devtools
82 |
83 |
84 |
85 | org.springframework.boot
86 | spring-boot-starter-test
87 | test
88 |
89 |
90 |
91 | org.springframework.boot
92 | spring-boot-starter-data-mongodb
93 |
94 |
95 |
96 | org.springframework.boot
97 | spring-boot-starter-hateoas
98 |
99 |
100 |
101 | org.springdoc
102 | springdoc-openapi-starter-webmvc-ui
103 | 2.8.5
104 |
105 |
106 |
107 | io.swagger
108 | swagger-annotations
109 | 1.6.15
110 |
111 |
112 | io.swagger
113 | swagger-models
114 | 1.6.15
115 |
116 |
117 |
118 | com.fasterxml.jackson.datatype
119 | jackson-datatype-jsr310
120 | 2.18.2
121 |
122 |
123 |
124 | org.apache.commons
125 | commons-lang3
126 | ${apache.commons.lang3.version}
127 |
128 |
129 |
130 | org.projectlombok
131 | lombok
132 | ${lombok.version}
133 | provided
134 |
135 |
136 |
137 | org.hibernate
138 | hibernate-validator
139 | ${hibernate.validator.version}
140 |
141 |
142 |
143 | org.glassfish
144 | jakarta.el
145 | 4.0.2
146 |
147 |
148 |
149 | com.jayway.jsonpath
150 | json-path-assert
151 | ${json-path.version}
152 | test
153 |
154 |
155 |
156 | de.flapdoodle.embed
157 | de.flapdoodle.embed.mongo
158 | 4.16.2
159 | test
160 |
161 |
162 |
163 |
164 | completeSpringProject
165 |
166 |
167 |
168 | org.springframework.boot
169 | spring-boot-maven-plugin
170 |
171 |
172 |
173 | org.jacoco
174 | jacoco-maven-plugin
175 | ${jacoco.mavenplugin.version}
176 |
177 |
178 | **/com/edwise/completespring/config/*
179 |
180 |
181 |
182 |
183 |
184 | ${sonar.jacoco.reportPath}
185 |
186 | pre-unit-test
187 |
188 | prepare-agent
189 |
190 |
191 |
192 | post-unit-test
193 | test
194 |
195 | report
196 |
197 |
198 |
199 |
200 | ${sonar.jacoco.itReportPath}
201 |
202 | pre-integration-test
203 | pre-integration-test
204 |
205 | prepare-agent-integration
206 |
207 |
208 |
209 |
210 |
211 |
212 | org.apache.maven.plugins
213 | maven-surefire-plugin
214 | 3.5.2
215 |
216 | ${skip.unit.tests}
217 |
218 | **/IT*.java
219 |
220 |
221 |
222 |
223 |
224 | org.codehaus.mojo
225 | build-helper-maven-plugin
226 | 3.6.0
227 |
228 |
229 | add-integration-test-sources
230 | generate-test-sources
231 |
232 | add-test-source
233 |
234 |
235 |
236 | src/integration-test/java
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 | org.apache.maven.plugins
245 | maven-failsafe-plugin
246 | 3.5.2
247 |
248 |
249 | integration-tests
250 |
251 | integration-test
252 | verify
253 |
254 |
255 | ${skip.integration.tests}
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
--------------------------------------------------------------------------------
/src/integration-test/java/com/edwise/completespring/controllers/ITActuatorEndpointsTest.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.controllers;
2 |
3 | import com.edwise.completespring.Application;
4 | import com.edwise.completespring.dbutils.DataLoader;
5 | import org.junit.jupiter.api.BeforeEach;
6 | import org.junit.jupiter.api.Test;
7 | import org.junit.jupiter.api.extension.ExtendWith;
8 | import org.mockito.MockitoAnnotations;
9 | import org.mockito.junit.jupiter.MockitoExtension;
10 | import org.springframework.beans.factory.annotation.Autowired;
11 | import org.springframework.boot.test.context.SpringBootTest;
12 | import org.springframework.http.MediaType;
13 | import org.springframework.test.web.servlet.MockMvc;
14 | import org.springframework.test.web.servlet.setup.MockMvcBuilders;
15 | import org.springframework.web.context.WebApplicationContext;
16 |
17 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
18 | import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
19 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
20 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
21 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
22 |
23 | @ExtendWith(MockitoExtension.class)
24 | @SpringBootTest(classes = {Application.class}, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
25 | public class ITActuatorEndpointsTest {
26 | private static final String NOT_EXISTING_USER_USERNAME = "notExists";
27 | private static final String NOT_EXISTING_USER_PASSWORD = "password3456";
28 |
29 | private MockMvc mockMvc;
30 |
31 | @Autowired
32 | protected WebApplicationContext webApplicationContext;
33 |
34 | @BeforeEach
35 | public void setUp() {
36 | MockitoAnnotations.initMocks(this);
37 | mockMvc = MockMvcBuilders
38 | .webAppContextSetup(this.webApplicationContext)
39 | .apply(springSecurity())
40 | .build();
41 | }
42 |
43 | @Test
44 | public void getInfoActuatorEnpoint_CorrectUser_ShouldReturnOkCode() throws Exception {
45 | mockMvc.perform(get("/actuator/info/")
46 | .with(httpBasic(DataLoader.ADMIN, DataLoader.PASSWORD_ADMIN))
47 | .accept(MediaType.APPLICATION_JSON))
48 | .andExpect(status().isOk())
49 | .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
50 | ;
51 | }
52 |
53 | @Test
54 | public void getInfoActuatorEnpoint_InCorrectUser_ShouldReturnForbiddenCode() throws Exception {
55 | mockMvc.perform(get("/actuator/info/")
56 | .with(httpBasic(DataLoader.USER, DataLoader.PASSWORD_USER))
57 | .accept(MediaType.APPLICATION_JSON))
58 | .andExpect(status().isForbidden())
59 | ;
60 | }
61 |
62 | @Test
63 | public void getInfoActuatorEnpoint_NotExistingUser_ShouldReturnUnauthorizedCode() throws Exception {
64 | mockMvc.perform(get("/actuator/info/")
65 | .with(httpBasic(NOT_EXISTING_USER_USERNAME, NOT_EXISTING_USER_PASSWORD))
66 | .accept(MediaType.APPLICATION_JSON))
67 | .andExpect(status().isUnauthorized())
68 | ;
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/src/integration-test/java/com/edwise/completespring/controllers/ITBookControllerTest.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.controllers;
2 |
3 | import com.edwise.completespring.Application;
4 | import com.edwise.completespring.dbutils.DataLoader;
5 | import com.edwise.completespring.entities.Author;
6 | import com.edwise.completespring.entities.Book;
7 | import com.edwise.completespring.entities.Publisher;
8 | import com.edwise.completespring.testutil.BookBuilder;
9 | import com.edwise.completespring.testutil.IntegrationTestUtil;
10 | import org.junit.jupiter.api.BeforeEach;
11 | import org.junit.jupiter.api.Test;
12 | import org.junit.jupiter.api.extension.ExtendWith;
13 | import org.mockito.MockitoAnnotations;
14 | import org.mockito.junit.jupiter.MockitoExtension;
15 | import org.springframework.beans.factory.annotation.Autowired;
16 | import org.springframework.boot.test.context.SpringBootTest;
17 | import org.springframework.http.MediaType;
18 | import org.springframework.test.web.servlet.MockMvc;
19 | import org.springframework.test.web.servlet.setup.MockMvcBuilders;
20 | import org.springframework.web.context.WebApplicationContext;
21 |
22 | import java.time.LocalDate;
23 | import java.util.List;
24 |
25 | import static com.edwise.completespring.testutil.IsValidFormatDateYMDMatcher.validFormatDateYMD;
26 | import static org.hamcrest.Matchers.*;
27 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
28 | import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
29 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
30 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
31 |
32 | /**
33 | * TODO this tests are executed with the same data that is charged only ONCE... maybe is needed to load data with each test.
34 | */
35 | @ExtendWith(MockitoExtension.class)
36 | @SpringBootTest(classes = {Application.class},
37 | webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
38 | public class ITBookControllerTest {
39 | private static final Long BOOK_ID_NOT_EXISTS = 111L;
40 | private static final String BOOK_TITLE_TEST1 = "Lord of the Rings";
41 | private static final LocalDate BOOK_RELEASEDATE_TEST1 = LocalDate.of(2013, 1, 26);
42 | private static final String BOOK_ISBN_TEST1 = "11-333-12";
43 | private static final String BOOK_ISBN_TEST2 = "11-666-77";
44 | private static final String PUBLISHER_NAME_TEST1 = "Planeta";
45 | private static final String PUBLISHER_COUNTRY_TEST1 = "ES";
46 | private static final String AUTHOR_NAME_TEST1 = "J.R.R.";
47 | private static final String AUTHOR_NAME_TEST2 = "William";
48 | private static final String AUTHOR_SURNAME_TEST1 = "Tolkien";
49 | private static final String BOOK_NOT_FOUND_EXCEPTION_MSG = "Book not found";
50 | private static final String NOT_EXISTING_USER_USERNAME = "notExistUser";
51 | private static final String NOT_EXISTING_USER_PASSWORD = "password2";
52 |
53 | private MockMvc mockMvc;
54 |
55 | @Autowired
56 | protected WebApplicationContext webApplicationContext;
57 |
58 | @BeforeEach
59 | public void setUp() {
60 | MockitoAnnotations.initMocks(this);
61 | mockMvc = MockMvcBuilders
62 | .webAppContextSetup(this.webApplicationContext)
63 | .apply(springSecurity())
64 | .build();
65 | }
66 |
67 | @Test
68 | public void getAll_CorrectUserAndBooksFound_ShouldReturnFoundBooks() throws Exception {
69 | mockMvc.perform(get("/api/books/")
70 | .with(httpBasic(DataLoader.USER, DataLoader.PASSWORD_USER))
71 | .accept(MediaType.APPLICATION_JSON))
72 | .andExpect(status().isOk())
73 | .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
74 | .andExpect(jsonPath("$", hasSize(greaterThan(1))))
75 | .andExpect(jsonPath("$[0].book").exists())
76 | .andExpect(jsonPath("$[0].book.id", is(notNullValue())))
77 | .andExpect(jsonPath("$[0].book.title", is(notNullValue())))
78 | .andExpect(jsonPath("$[0].book.authors", is(notNullValue())))
79 | .andExpect(jsonPath("$[0].book.isbn", is(notNullValue())))
80 | .andExpect(jsonPath("$[0].book.releaseDate", is(notNullValue())))
81 | .andExpect(jsonPath("$[0].book.releaseDate", is(validFormatDateYMD())))
82 | .andExpect(jsonPath("$[0].book.publisher", is(notNullValue())))
83 | .andExpect(jsonPath("$[0].links", hasSize(1)))
84 | .andExpect(jsonPath("$[0].links[0].rel", is(notNullValue())))
85 | .andExpect(jsonPath("$[0].links[0].href", containsString("/api/books/")))
86 | ;
87 | }
88 |
89 | @Test
90 | public void getAll_NotExistingUser_ShouldReturnUnauthorizedCode() throws Exception {
91 | mockMvc.perform(get("/api/books/").with(httpBasic(NOT_EXISTING_USER_USERNAME, NOT_EXISTING_USER_PASSWORD)))
92 | .andExpect(status().isUnauthorized());
93 | }
94 |
95 | @Test
96 | public void getBook_CorrectUserAndBookFound_ShouldReturnCorrectBook() throws Exception {
97 | mockMvc.perform(get("/api/books/{id}", DataLoader.BOOK_ID_1)
98 | .with(httpBasic(DataLoader.USER, DataLoader.PASSWORD_USER))
99 | .accept(MediaType.APPLICATION_JSON))
100 | .andExpect(status().isOk())
101 | .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
102 | .andExpect(jsonPath("$").exists())
103 | .andExpect(jsonPath("$.book").exists())
104 | .andExpect(jsonPath("$.book.id", is(DataLoader.BOOK_ID_1.intValue())))
105 | .andExpect(jsonPath("$.book.title", is(notNullValue())))
106 | .andExpect(jsonPath("$.book.authors", is(notNullValue())))
107 | .andExpect(jsonPath("$.book.isbn", is(notNullValue())))
108 | .andExpect(jsonPath("$.book.releaseDate", is(notNullValue())))
109 | .andExpect(jsonPath("$.book.releaseDate", is(validFormatDateYMD())))
110 | .andExpect(jsonPath("$.book.publisher", is(notNullValue())))
111 | .andExpect(jsonPath("$._links").exists())
112 | .andExpect(jsonPath("$._links.self.href", containsString("/api/books/" + DataLoader.BOOK_ID_1)))
113 | ;
114 | }
115 |
116 | @Test
117 | public void getBook_CorrectUserAndBookNotFound_ShouldReturnNotFoundStatusAndError() throws Exception {
118 | mockMvc.perform(get("/api/books/{id}", BOOK_ID_NOT_EXISTS)
119 | .with(httpBasic(DataLoader.USER, DataLoader.PASSWORD_USER))
120 | .accept(MediaType.APPLICATION_JSON))
121 | .andExpect(status().isNotFound())
122 | .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
123 | .andExpect(jsonPath("$").exists())
124 | .andExpect(jsonPath("$.errors", hasSize(1)))
125 | .andExpect(jsonPath("$.errors[0].field", is("id")))
126 | .andExpect(jsonPath("$.errors[0].message", is(BOOK_NOT_FOUND_EXCEPTION_MSG)))
127 | ;
128 | }
129 |
130 | @Test
131 | public void getBook_NotExistingUser_ShouldReturnUnauthorizedCode() throws Exception {
132 | mockMvc.perform(get("/api/books/{id}", DataLoader.BOOK_ID_1)
133 | .with(httpBasic(NOT_EXISTING_USER_USERNAME, NOT_EXISTING_USER_PASSWORD)))
134 | .andExpect(status().isUnauthorized())
135 | ;
136 | }
137 |
138 | @Test
139 | public void postBook_CorrectUserAndBookCorrect_ShouldReturnCreatedStatusAndCorrectBook() throws Exception {
140 | Book bookToCreate = new BookBuilder()
141 | .title(BOOK_TITLE_TEST1)
142 | .authors(List.of(new Author().setName(AUTHOR_NAME_TEST1).setSurname(AUTHOR_SURNAME_TEST1)))
143 | .isbn(BOOK_ISBN_TEST1)
144 | .releaseDate(BOOK_RELEASEDATE_TEST1)
145 | .publisher(new Publisher().setName(PUBLISHER_NAME_TEST1).setCountry(PUBLISHER_COUNTRY_TEST1).setOnline(false))
146 | .build();
147 |
148 | mockMvc.perform(post("/api/books/")
149 | .contentType(MediaType.APPLICATION_JSON)
150 | .content(IntegrationTestUtil.convertObjectToJsonBytes(bookToCreate))
151 | .with(httpBasic(DataLoader.USER, DataLoader.PASSWORD_USER)))
152 | .andExpect(status().isCreated())
153 | .andExpect(header().string("Location", containsString("/api/books/")))
154 | ;
155 | }
156 |
157 | @Test
158 | public void postBook_CorrectUserAndBookIncorrect_ShouldReturnBadRequestStatusAndError() throws Exception {
159 | Book bookToCreate = new BookBuilder()
160 | .authors(List.of(new Author().setName(AUTHOR_NAME_TEST1).setSurname(AUTHOR_SURNAME_TEST1)))
161 | .publisher(new Publisher().setName(PUBLISHER_NAME_TEST1).setCountry(PUBLISHER_COUNTRY_TEST1).setOnline(false))
162 | .build();
163 |
164 | mockMvc.perform(post("/api/books/")
165 | .contentType(MediaType.APPLICATION_JSON)
166 | .content(IntegrationTestUtil.convertObjectToJsonBytes(bookToCreate))
167 | .with(httpBasic(DataLoader.USER, DataLoader.PASSWORD_USER))
168 | .accept(MediaType.APPLICATION_JSON))
169 | .andExpect(status().isBadRequest())
170 | .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
171 | .andExpect(jsonPath("$").exists())
172 | .andExpect(jsonPath("$.errors", hasSize(3)))
173 | .andExpect(jsonPath("$.errors[*].field", containsInAnyOrder("title", "isbn", "releaseDate")))
174 | .andExpect(jsonPath("$.errors[*].message", is(notNullValue())))
175 | ;
176 | }
177 |
178 | @Test
179 | public void postBook_NotExistingUser_ShouldReturnUnauthorizedCode() throws Exception {
180 | Book bookToCreate = new BookBuilder()
181 | .title(BOOK_TITLE_TEST1)
182 | .authors(List.of(new Author().setName(AUTHOR_NAME_TEST1).setSurname(AUTHOR_SURNAME_TEST1)))
183 | .isbn(BOOK_ISBN_TEST1)
184 | .releaseDate(BOOK_RELEASEDATE_TEST1)
185 | .publisher(new Publisher().setName(PUBLISHER_NAME_TEST1).setCountry(PUBLISHER_COUNTRY_TEST1).setOnline(false))
186 | .build();
187 |
188 | mockMvc.perform(post("/api/books/")
189 | .contentType(MediaType.APPLICATION_JSON)
190 | .content(IntegrationTestUtil.convertObjectToJsonBytes(bookToCreate))
191 | .with(httpBasic(NOT_EXISTING_USER_USERNAME, NOT_EXISTING_USER_PASSWORD)))
192 | .andExpect(status().isUnauthorized())
193 | ;
194 | }
195 |
196 | @Test
197 | public void putBook_CorrectUserAndBookExist_ShouldReturnNoContentStatus() throws Exception {
198 | Book bookToUpdate = new BookBuilder()
199 | .title(BOOK_TITLE_TEST1)
200 | .authors(List.of(new Author().setName(AUTHOR_NAME_TEST2).setSurname(AUTHOR_SURNAME_TEST1)))
201 | .isbn(BOOK_ISBN_TEST2)
202 | .releaseDate(BOOK_RELEASEDATE_TEST1)
203 | .publisher(new Publisher().setName(PUBLISHER_NAME_TEST1).setCountry(PUBLISHER_COUNTRY_TEST1).setOnline(true))
204 | .build();
205 |
206 | mockMvc.perform(put("/api/books/{id}", DataLoader.BOOK_ID_2)
207 | .contentType(MediaType.APPLICATION_JSON)
208 | .content(IntegrationTestUtil.convertObjectToJsonBytes(bookToUpdate))
209 | .with(httpBasic(DataLoader.USER, DataLoader.PASSWORD_USER)))
210 | .andExpect(status().isNoContent())
211 | ;
212 | }
213 |
214 | @Test
215 | public void putBook_CorrectUserAndBookNotExists_ShouldReturnNotFoundStatusAndError() throws Exception {
216 | Book bookToUpdate = new BookBuilder()
217 | .title(BOOK_TITLE_TEST1)
218 | .authors(List.of(new Author().setName(AUTHOR_NAME_TEST2).setSurname(AUTHOR_SURNAME_TEST1)))
219 | .isbn(BOOK_ISBN_TEST2)
220 | .releaseDate(BOOK_RELEASEDATE_TEST1)
221 | .publisher(new Publisher().setName(PUBLISHER_NAME_TEST1).setCountry(PUBLISHER_COUNTRY_TEST1).setOnline(true))
222 | .build();
223 |
224 | mockMvc.perform(put("/api/books/{id}", BOOK_ID_NOT_EXISTS)
225 | .contentType(MediaType.APPLICATION_JSON)
226 | .content(IntegrationTestUtil.convertObjectToJsonBytes(bookToUpdate))
227 | .with(httpBasic(DataLoader.USER, DataLoader.PASSWORD_USER))
228 | .accept(MediaType.APPLICATION_JSON))
229 | .andExpect(status().isNotFound())
230 | .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
231 | .andExpect(jsonPath("$.errors", hasSize(1)))
232 | .andExpect(jsonPath("$.errors[0].field", is("id")))
233 | .andExpect(jsonPath("$.errors[0].message", is(BOOK_NOT_FOUND_EXCEPTION_MSG)))
234 | ;
235 | }
236 |
237 | @Test
238 | public void putBook_CorrectUserAndBookIncorrect_ShouldReturnBadRequestStatusAndError() throws Exception {
239 | Book bookToUpdate = new BookBuilder()
240 | .authors(List.of(new Author().setName(AUTHOR_NAME_TEST2).setSurname(AUTHOR_SURNAME_TEST1)))
241 | .publisher(new Publisher().setName(PUBLISHER_NAME_TEST1).setCountry(PUBLISHER_COUNTRY_TEST1).setOnline(true))
242 | .build();
243 |
244 | mockMvc.perform(put("/api/books/{id}", DataLoader.BOOK_ID_2)
245 | .with(httpBasic(DataLoader.USER, DataLoader.PASSWORD_USER))
246 | .contentType(MediaType.APPLICATION_JSON)
247 | .content(IntegrationTestUtil.convertObjectToJsonBytes(bookToUpdate))
248 | .accept(MediaType.APPLICATION_JSON))
249 | .andExpect(status().isBadRequest())
250 | .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
251 | .andExpect(jsonPath("$").exists())
252 | .andExpect(jsonPath("$.errors", hasSize(3)))
253 | .andExpect(jsonPath("$.errors[*].field", containsInAnyOrder("title", "isbn", "releaseDate")))
254 | .andExpect(jsonPath("$.errors[*].message", is(notNullValue())))
255 | ;
256 | }
257 |
258 | @Test
259 | public void putBook_NotExistingUser_ShouldReturnUnauthorizedCode() throws Exception {
260 | Book bookToUpdate = new BookBuilder()
261 | .title(BOOK_TITLE_TEST1)
262 | .authors(List.of(new Author().setName(AUTHOR_NAME_TEST2).setSurname(AUTHOR_SURNAME_TEST1)))
263 | .isbn(BOOK_ISBN_TEST2)
264 | .releaseDate(BOOK_RELEASEDATE_TEST1)
265 | .publisher(new Publisher().setName(PUBLISHER_NAME_TEST1).setCountry(PUBLISHER_COUNTRY_TEST1).setOnline(true))
266 | .build();
267 |
268 | mockMvc.perform(put("/api/books/{id}", DataLoader.BOOK_ID_2)
269 | .contentType(MediaType.APPLICATION_JSON)
270 | .content(IntegrationTestUtil.convertObjectToJsonBytes(bookToUpdate))
271 | .with(httpBasic(NOT_EXISTING_USER_USERNAME, NOT_EXISTING_USER_PASSWORD)))
272 | .andExpect(status().isUnauthorized())
273 | ;
274 | }
275 |
276 | @Test
277 | public void deleteBook_CorrectUserAndBookExist_ShouldReturnNoContentStatus() throws Exception {
278 | mockMvc.perform(delete("/api/books/{id}", DataLoader.BOOK_ID_3)
279 | .with(httpBasic(DataLoader.USER, DataLoader.PASSWORD_USER)))
280 | .andExpect(status().isNoContent())
281 | ;
282 | }
283 |
284 | @Test
285 | public void deleteBook_NotExistingUser_ShouldReturnUnauthorizedCode() throws Exception {
286 | mockMvc.perform(delete("/api/books/{id}", DataLoader.BOOK_ID_3)
287 | .with(httpBasic(NOT_EXISTING_USER_USERNAME, NOT_EXISTING_USER_PASSWORD)))
288 | .andExpect(status().isUnauthorized())
289 | ;
290 | }
291 |
292 | }
293 |
--------------------------------------------------------------------------------
/src/integration-test/java/com/edwise/completespring/controllers/ITFooControllerTest.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.controllers;
2 |
3 | import com.edwise.completespring.Application;
4 | import com.edwise.completespring.dbutils.DataLoader;
5 | import com.edwise.completespring.entities.Foo;
6 | import com.edwise.completespring.entities.FooTest;
7 | import com.edwise.completespring.testutil.IntegrationTestUtil;
8 | import org.junit.jupiter.api.BeforeEach;
9 | import org.junit.jupiter.api.Test;
10 | import org.junit.jupiter.api.extension.ExtendWith;
11 | import org.mockito.MockitoAnnotations;
12 | import org.mockito.junit.jupiter.MockitoExtension;
13 | import org.springframework.beans.factory.annotation.Autowired;
14 | import org.springframework.boot.test.context.SpringBootTest;
15 | import org.springframework.http.MediaType;
16 | import org.springframework.test.web.servlet.MockMvc;
17 | import org.springframework.test.web.servlet.setup.MockMvcBuilders;
18 | import org.springframework.web.context.WebApplicationContext;
19 |
20 | import java.time.LocalDate;
21 |
22 | import static com.edwise.completespring.testutil.IsValidFormatDateYMDMatcher.validFormatDateYMD;
23 | import static org.hamcrest.Matchers.*;
24 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
25 | import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
26 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
27 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
28 |
29 | @ExtendWith(MockitoExtension.class)
30 | @SpringBootTest(classes = {Application.class}, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
31 | public class ITFooControllerTest {
32 | private static final long FOO_ID_TEST1 = 1L;
33 | private static final String ATT_TEXT_1 = "AttText1";
34 | private static final String FOO_TEXT_ATTR_TEST1 = ATT_TEXT_1;
35 | private static final LocalDate DATE_TEST1 = LocalDate.of(2013, 1, 26);
36 | private static final String NOT_EXISTING_USER_USERNAME = "inCorrectUser";
37 | private static final String NOT_EXISTING_USER_PASSWORD = "password2";
38 |
39 | private MockMvc mockMvc;
40 |
41 | @Autowired
42 | protected WebApplicationContext webApplicationContext;
43 |
44 | @BeforeEach
45 | public void setUp() {
46 | MockitoAnnotations.initMocks(this);
47 | mockMvc = MockMvcBuilders
48 | .webAppContextSetup(this.webApplicationContext)
49 | .apply(springSecurity())
50 | .build();
51 | }
52 |
53 | @Test
54 | public void getAll_CorrectUserAndFoosFound_ShouldReturnFoundFoos() throws Exception {
55 | mockMvc.perform(get("/api/foos/")
56 | .with(httpBasic(DataLoader.USER, DataLoader.PASSWORD_USER))
57 | .accept(MediaType.APPLICATION_JSON))
58 | .andExpect(status().isOk())
59 | .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
60 | .andExpect(jsonPath("$", hasSize(2)))
61 | .andExpect(jsonPath("$[0].foo").exists())
62 | .andExpect(jsonPath("$[0].foo.id", is(1)))
63 | .andExpect(jsonPath("$[0].foo.sampleTextAttribute", is(ATT_TEXT_1)))
64 | .andExpect(jsonPath("$[0].foo.sampleLocalDateAttribute", is(notNullValue())))
65 | .andExpect(jsonPath("$[0].links", hasSize(1)))
66 | .andExpect(jsonPath("$[0].links[0].rel", is(notNullValue())))
67 | .andExpect(jsonPath("$[0].links[0].href", containsString("/api/foos/1")))
68 | .andExpect(jsonPath("$[1].foo").exists())
69 | .andExpect(jsonPath("$[1].foo.id", is(2)))
70 | .andExpect(jsonPath("$[1].foo.sampleTextAttribute", is(ATT_TEXT_1)))
71 | .andExpect(jsonPath("$[1].foo.sampleLocalDateAttribute", is(notNullValue())))
72 | .andExpect(jsonPath("$[1].links", hasSize(1)))
73 | .andExpect(jsonPath("$[1].links[0].rel", is(notNullValue())))
74 | .andExpect(jsonPath("$[1].links[0].href", containsString("/api/foos/2")))
75 | ;
76 | }
77 |
78 | @Test
79 | public void getAll_InCorrectUser_ShouldReturnUnauthorizedCode() throws Exception {
80 | mockMvc.perform(get("/api/foos/").with(httpBasic(NOT_EXISTING_USER_USERNAME, NOT_EXISTING_USER_PASSWORD)))
81 | .andExpect(status().isUnauthorized())
82 | ;
83 | }
84 |
85 | @Test
86 | public void getFoo_CorrectUserAndFooFound_ShouldReturnCorrectFoo() throws Exception {
87 | mockMvc.perform(get("/api/foos/{id}", FOO_ID_TEST1)
88 | .with(httpBasic(DataLoader.USER, DataLoader.PASSWORD_USER))
89 | .accept(MediaType.APPLICATION_JSON))
90 | .andExpect(status().isOk())
91 | .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
92 | .andExpect(jsonPath("$").exists())
93 | .andExpect(jsonPath("$.foo").exists())
94 | .andExpect(jsonPath("$.foo.id", is(1)))
95 | .andExpect(jsonPath("$.foo.sampleTextAttribute", is(ATT_TEXT_1)))
96 | .andExpect(jsonPath("$.foo.sampleLocalDateAttribute", is(notNullValue())))
97 | .andExpect(jsonPath("$.foo.sampleLocalDateAttribute", is(validFormatDateYMD())))
98 | .andExpect(jsonPath("$._links").exists())
99 | .andExpect(jsonPath("$._links.self.href", containsString("/api/foos/" + FOO_ID_TEST1)))
100 | ;
101 | }
102 |
103 | @Test
104 | public void getFoo_InCorrectUser_ShouldReturnUnauthorizedCode() throws Exception {
105 | mockMvc.perform(get("/api/foos/{id}", FOO_ID_TEST1)
106 | .with(httpBasic(NOT_EXISTING_USER_USERNAME, NOT_EXISTING_USER_PASSWORD)))
107 | .andExpect(status().isUnauthorized())
108 | ;
109 | }
110 |
111 | @Test
112 | public void postFoo_CorrectUserAndFooCorrect_ShouldReturnCreatedStatus() throws Exception {
113 | Foo fooToCreate = FooTest.createFoo(null, FOO_TEXT_ATTR_TEST1, DATE_TEST1);
114 |
115 | mockMvc.perform(post("/api/foos/")
116 | .contentType(MediaType.APPLICATION_JSON)
117 | .content(IntegrationTestUtil.convertObjectToJsonBytes(fooToCreate))
118 | .with(httpBasic(DataLoader.USER, DataLoader.PASSWORD_USER)))
119 | .andExpect(status().isCreated())
120 | .andExpect(header().string("Location", containsString("/api/foos/" + FOO_ID_TEST1)))
121 | ;
122 | }
123 |
124 | @Test
125 | public void postFoo_CorrectUserAndFooIncorrect_ShouldReturnBadRequestStatusAndError() throws Exception {
126 | Foo fooToCreate = FooTest.createFoo(null, null, null); // text and date as null
127 |
128 | mockMvc.perform(post("/api/foos/")
129 | .contentType(MediaType.APPLICATION_JSON)
130 | .content(IntegrationTestUtil.convertObjectToJsonBytes(fooToCreate))
131 | .with(httpBasic(DataLoader.USER, DataLoader.PASSWORD_USER))
132 | .accept(MediaType.APPLICATION_JSON))
133 | .andExpect(status().isBadRequest())
134 | .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
135 | .andExpect(jsonPath("$").exists())
136 | .andExpect(jsonPath("$.errors", hasSize(2)))
137 | .andExpect(jsonPath("$.errors[*].field", containsInAnyOrder("sampleTextAttribute", "sampleLocalDateAttribute")))
138 | .andExpect(jsonPath("$.errors[*].message", is(notNullValue())))
139 | ;
140 | }
141 |
142 | @Test
143 | public void postFoo_InCorrectUser_ShouldReturnUnauthorizedCode() throws Exception {
144 | Foo fooToCreate = FooTest.createFoo(null, FOO_TEXT_ATTR_TEST1, DATE_TEST1);
145 |
146 | mockMvc.perform(post("/api/foos/")
147 | .contentType(MediaType.APPLICATION_JSON)
148 | .content(IntegrationTestUtil.convertObjectToJsonBytes(fooToCreate))
149 | .with(httpBasic(NOT_EXISTING_USER_USERNAME, NOT_EXISTING_USER_PASSWORD)))
150 | .andExpect(status().isUnauthorized())
151 | ;
152 | }
153 |
154 | @Test
155 | public void putFoo_CorrectUserAndFooExist_ShouldReturnCreatedStatus() throws Exception {
156 | Foo fooWithChangedFields = FooTest.createFoo(null, FOO_TEXT_ATTR_TEST1, DATE_TEST1);
157 |
158 | mockMvc.perform(put("/api/foos/{id}", FOO_ID_TEST1)
159 | .contentType(MediaType.APPLICATION_JSON)
160 | .content(IntegrationTestUtil.convertObjectToJsonBytes(fooWithChangedFields))
161 | .with(httpBasic(DataLoader.USER, DataLoader.PASSWORD_USER)))
162 | .andExpect(status().isNoContent())
163 | ;
164 | }
165 |
166 | @Test
167 | public void putFoo_CorrectUserAndFooIncorrect_ShouldReturnBadRequestStatusAndError() throws Exception {
168 | Foo fooWithChangedFields = FooTest.createFoo(null, null, null); // text and date as null
169 |
170 | mockMvc.perform(put("/api/foos/{id}", FOO_ID_TEST1)
171 | .contentType(MediaType.APPLICATION_JSON)
172 | .content(IntegrationTestUtil.convertObjectToJsonBytes(fooWithChangedFields))
173 | .with(httpBasic(DataLoader.USER, DataLoader.PASSWORD_USER))
174 | .accept(MediaType.APPLICATION_JSON))
175 | .andExpect(status().isBadRequest())
176 | .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
177 | .andExpect(jsonPath("$").exists())
178 | .andExpect(jsonPath("$.errors", hasSize(2)))
179 | .andExpect(jsonPath("$.errors[*].field", containsInAnyOrder("sampleTextAttribute", "sampleLocalDateAttribute")))
180 | .andExpect(jsonPath("$.errors[*].message", is(notNullValue())))
181 | ;
182 | }
183 |
184 | @Test
185 | public void putFoo_InCorrectUser_ShouldReturnUnauthorizedCode() throws Exception {
186 | Foo fooWithChangedFields = FooTest.createFoo(null, FOO_TEXT_ATTR_TEST1, DATE_TEST1);
187 |
188 | mockMvc.perform(put("/api/foos/{id}", FOO_ID_TEST1)
189 | .contentType(MediaType.APPLICATION_JSON)
190 | .content(IntegrationTestUtil.convertObjectToJsonBytes(fooWithChangedFields))
191 | .with(httpBasic(NOT_EXISTING_USER_USERNAME, NOT_EXISTING_USER_PASSWORD)))
192 | .andExpect(status().isUnauthorized())
193 | ;
194 | }
195 |
196 | @Test
197 | public void deleteFoo_CorrectUserAndFooExist_ShouldReturnNoContentStatus() throws Exception {
198 | mockMvc.perform(delete("/api/foos/{id}", FOO_ID_TEST1)
199 | .with(httpBasic(DataLoader.USER, DataLoader.PASSWORD_USER)))
200 | .andExpect(status().isNoContent())
201 | ;
202 | }
203 |
204 | @Test
205 | public void deleteFoo_InCorrectUser_ShouldReturnUnauthorizedCode() throws Exception {
206 | mockMvc.perform(delete("/api/foos/{id}", FOO_ID_TEST1)
207 | .with(httpBasic(NOT_EXISTING_USER_USERNAME, NOT_EXISTING_USER_PASSWORD)))
208 | .andExpect(status().isUnauthorized())
209 | ;
210 | }
211 |
212 | }
213 |
--------------------------------------------------------------------------------
/src/integration-test/java/com/edwise/completespring/testswagger/ITCommonSwaggerAPITest.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.testswagger;
2 |
3 | import com.edwise.completespring.Application;
4 | import com.edwise.completespring.config.SwaggerConfig;
5 | import org.junit.jupiter.api.BeforeEach;
6 | import org.junit.jupiter.api.Test;
7 | import org.junit.jupiter.api.extension.ExtendWith;
8 | import org.mockito.junit.jupiter.MockitoExtension;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.boot.test.context.SpringBootTest;
11 | import org.springframework.test.web.servlet.MockMvc;
12 | import org.springframework.test.web.servlet.setup.MockMvcBuilders;
13 | import org.springframework.web.context.WebApplicationContext;
14 |
15 | import static org.hamcrest.Matchers.is;
16 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
17 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
18 |
19 | @ExtendWith(MockitoExtension.class)
20 | @SpringBootTest(classes = {Application.class}, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
21 | public class ITCommonSwaggerAPITest {
22 |
23 | private MockMvc mockMvc;
24 |
25 | @Autowired
26 | protected WebApplicationContext webApplicationContext;
27 |
28 | @BeforeEach
29 | public void setUp() {
30 | mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();
31 | }
32 |
33 | @Test
34 | public void getApiDocsSwagger_shouldReturnGeneralInfoOfAPI() throws Exception {
35 | mockMvc.perform(get("/v2/api-docs").param("group", SwaggerConfig.BOOKS_API_GROUP))
36 | .andExpect(status().isOk())
37 | .andExpect(content().contentTypeCompatibleWith("application/json"))
38 | .andExpect(jsonPath("$.info").exists())
39 | .andExpect(jsonPath("$.info.title", is("Books API")))
40 | .andExpect(jsonPath("$.info.description", is("Your book database!")))
41 | .andExpect(jsonPath("$.info.termsOfService", is("http://en.wikipedia.org/wiki/Terms_of_service")))
42 | .andExpect(jsonPath("$.info.contact.email", is("edwise.null@gmail.com")))
43 | .andExpect(jsonPath("$.info.license.name", is("Apache License Version 2.0")))
44 | .andExpect(jsonPath("$.info.license.url", is("http://www.apache.org/licenses/LICENSE-2.0.html")))
45 |
46 | ;
47 | }
48 |
49 | @Test
50 | public void getApiBooksDocsSwagger_shouldReturnBooksInfoOfAPI() throws Exception {
51 | mockMvc.perform(get("/v2/api-docs").param("group", SwaggerConfig.BOOKS_API_GROUP))
52 | .andExpect(status().isOk())
53 | .andExpect(content().contentTypeCompatibleWith("application/json"))
54 | .andExpect(jsonPath("$.paths").exists())
55 | .andExpect(jsonPath("$.paths./api/books/").exists())
56 | ;
57 | }
58 |
59 | @Test
60 | public void getApiFoosDocsSwagger_shouldReturnFoosInfoOfAPI() throws Exception {
61 | mockMvc.perform(get("/v2/api-docs").param("group", SwaggerConfig.BOOKS_API_GROUP))
62 | .andExpect(status().isOk())
63 | .andExpect(content().contentTypeCompatibleWith("application/json"))
64 | .andExpect(jsonPath("$.paths").exists())
65 | .andExpect(jsonPath("$.paths./api/foos/").exists())
66 | ;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/integration-test/java/com/edwise/completespring/testutil/IntegrationTestUtil.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.testutil;
2 |
3 | import com.fasterxml.jackson.annotation.JsonInclude;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
6 |
7 | import java.io.IOException;
8 |
9 | public class IntegrationTestUtil {
10 |
11 | private IntegrationTestUtil() {
12 | }
13 |
14 | public static byte[] convertObjectToJsonBytes(Object object) throws IOException {
15 | ObjectMapper mapper = new ObjectMapper();
16 | mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
17 | mapper.registerModule(new JavaTimeModule());
18 | mapper.configure(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
19 |
20 | return mapper.writeValueAsBytes(object);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/integration-test/java/com/edwise/completespring/testutil/IsValidFormatDateYMDMatcher.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.testutil;
2 |
3 | import org.hamcrest.Description;
4 | import org.hamcrest.Matcher;
5 | import org.hamcrest.TypeSafeMatcher;
6 |
7 | import java.util.regex.Pattern;
8 |
9 | public class IsValidFormatDateYMDMatcher extends TypeSafeMatcher {
10 |
11 | private static final String DATE_VALIDATION_PATTERN_YMD = "[0-9]{4}-[0-9]{2}-[0-9]{2}";
12 |
13 | private final Pattern pattern;
14 |
15 | public IsValidFormatDateYMDMatcher() {
16 | pattern = Pattern.compile(DATE_VALIDATION_PATTERN_YMD);
17 | }
18 |
19 | @Override
20 | protected boolean matchesSafely(String date) {
21 | boolean itMatches = false;
22 | if (date != null) {
23 | java.util.regex.Matcher matcher = pattern.matcher(date);
24 | itMatches = matcher.matches();
25 | }
26 | return itMatches;
27 | }
28 |
29 | @Override
30 | public void describeTo(Description description) {
31 | description.appendText("not a valid YMD format date");
32 | }
33 |
34 | public static Matcher validFormatDateYMD() {
35 | return new IsValidFormatDateYMDMatcher();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/integration-test/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/Application.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring;
2 |
3 | import com.edwise.completespring.dbutils.DataLoader;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.beans.factory.annotation.Value;
7 | import org.springframework.boot.CommandLineRunner;
8 | import org.springframework.boot.SpringApplication;
9 | import org.springframework.boot.autoconfigure.SpringBootApplication;
10 | import org.springframework.context.annotation.Bean;
11 |
12 | /**
13 | * Spring Boot Application class
14 | */
15 | @Slf4j
16 | @SpringBootApplication
17 | public class Application {
18 |
19 | @Autowired
20 | private DataLoader dataLoader;
21 |
22 | public static void main(String[] args) {
23 | SpringApplication.run(Application.class, args);
24 | }
25 |
26 | @Bean
27 | CommandLineRunner init(@Value("${db.resetAndLoadOnStartup:true}") boolean resetAndLoadDBDataOnStartup) {
28 | return args -> initApp(resetAndLoadDBDataOnStartup);
29 | }
30 |
31 | void initApp(boolean restAndLoadDBData) {
32 | log.info("Init Application...");
33 | if (restAndLoadDBData) {
34 | dataLoader.fillDBData();
35 | }
36 | log.info("Aplication initiated!");
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/ApplicationForServer.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring;
2 |
3 | import org.springframework.boot.builder.SpringApplicationBuilder;
4 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
5 |
6 | public class ApplicationForServer extends SpringBootServletInitializer {
7 |
8 | @Override
9 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
10 | return application.sources(Application.class);
11 | }
12 |
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/assemblers/BookResource.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.assemblers;
2 |
3 | import com.edwise.completespring.entities.Book;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 | import lombok.experimental.Accessors;
7 | import org.springframework.hateoas.RepresentationModel;
8 |
9 | @Getter
10 | @Setter
11 | @Accessors(chain = true)
12 | public class BookResource extends RepresentationModel {
13 | private Book book;
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/assemblers/BookResourceAssembler.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.assemblers;
2 |
3 | import com.edwise.completespring.controllers.BookController;
4 | import com.edwise.completespring.entities.Book;
5 | import org.springframework.stereotype.Component;
6 | import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport;
7 |
8 | import java.util.List;
9 |
10 | @Component
11 | public class BookResourceAssembler extends RepresentationModelAssemblerSupport {
12 |
13 | public BookResourceAssembler() {
14 | super(BookController.class, BookResource.class);
15 | }
16 |
17 | @Override
18 | protected BookResource instantiateModel(Book book) {
19 | BookResource bookResource = super.instantiateModel(book);
20 | bookResource.setBook(book);
21 |
22 | return bookResource;
23 | }
24 |
25 | @Override
26 | public BookResource toModel(Book book) {
27 | return createModelWithId(book.getId(), book);
28 | }
29 |
30 | public List toModels(List books) {
31 | return books.stream().map(this::toModel).toList();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/assemblers/FooResource.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.assemblers;
2 |
3 | import com.edwise.completespring.entities.Foo;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 | import lombok.experimental.Accessors;
7 | import org.springframework.hateoas.RepresentationModel;
8 |
9 | @Getter
10 | @Setter
11 | @Accessors(chain = true)
12 | public class FooResource extends RepresentationModel {
13 | private Foo foo;
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/assemblers/FooResourceAssembler.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.assemblers;
2 |
3 | import com.edwise.completespring.controllers.FooController;
4 | import com.edwise.completespring.entities.Book;
5 | import com.edwise.completespring.entities.Foo;
6 | import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport;
7 | import org.springframework.stereotype.Component;
8 |
9 | import java.util.List;
10 |
11 | @Component
12 | public class FooResourceAssembler extends RepresentationModelAssemblerSupport {
13 |
14 | public FooResourceAssembler() {
15 | super(FooController.class, FooResource.class);
16 | }
17 |
18 | @Override
19 | protected FooResource instantiateModel(Foo foo) {
20 | FooResource fooResource = super.instantiateModel(foo);
21 | fooResource.setFoo(foo);
22 |
23 | return fooResource;
24 | }
25 |
26 | public FooResource toModel(Foo foo) {
27 | return this.createModelWithId(foo.getId(), foo);
28 | }
29 |
30 | public List toModels(List foos) {
31 | return foos.stream().map(this::toModel).toList();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/config/SpringSecurityAuthenticationConfig.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.config;
2 |
3 | import com.edwise.completespring.entities.UserAccount;
4 | import com.edwise.completespring.repositories.UserAccountRepository;
5 | import lombok.extern.slf4j.Slf4j;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.context.annotation.Bean;
8 | import org.springframework.context.annotation.Configuration;
9 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
10 | import org.springframework.security.config.annotation.authentication.configuration.GlobalAuthenticationConfigurerAdapter;
11 | import org.springframework.security.core.userdetails.User;
12 | import org.springframework.security.core.userdetails.UserDetailsService;
13 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
14 |
15 | @Configuration
16 | @Slf4j
17 | public class SpringSecurityAuthenticationConfig extends GlobalAuthenticationConfigurerAdapter {
18 |
19 | @Autowired
20 | private UserAccountRepository userAccountRepository;
21 |
22 | @Override
23 | public void init(AuthenticationManagerBuilder auth) throws Exception {
24 | auth.userDetailsService(userDetailsService());
25 | }
26 |
27 | @Bean
28 | UserDetailsService userDetailsService() {
29 | return username -> {
30 | UserAccount userAccount = userAccountRepository.findByUsername(username);
31 | if (userAccount != null) {
32 | return User.withUsername(userAccount.getUsername())
33 | .password(userAccount.getPassword())
34 | .roles(userAccount.getUserType().toString())
35 | .build();
36 | } else {
37 | log.warn("Not existing user: {}", username);
38 | throw new UsernameNotFoundException("Could not find the user '" + username + "'");
39 | }
40 | };
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/config/SpringWebSecurityConfig.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.config;
2 |
3 | import com.edwise.completespring.entities.UserAccountType;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.context.annotation.Bean;
7 | import org.springframework.context.annotation.Configuration;
8 | import org.springframework.security.authentication.AuthenticationManager;
9 | import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
10 | import org.springframework.security.config.Customizer;
11 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
12 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
13 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
14 | import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
15 | import org.springframework.security.core.userdetails.UserDetailsService;
16 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
17 | import org.springframework.security.crypto.password.PasswordEncoder;
18 | import org.springframework.security.web.SecurityFilterChain;
19 |
20 | @EnableWebSecurity
21 | @Configuration
22 | @Slf4j
23 | public class SpringWebSecurityConfig {
24 |
25 | @Autowired
26 | private UserDetailsService userDetailsService;
27 |
28 | @Bean
29 | public DaoAuthenticationProvider authProvider() {
30 | DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
31 | authProvider.setUserDetailsService(userDetailsService);
32 | authProvider.setPasswordEncoder(passwordEncoder());
33 | return authProvider;
34 | }
35 |
36 | @Bean
37 | public PasswordEncoder passwordEncoder() {
38 | return new BCryptPasswordEncoder();
39 | }
40 |
41 | @Bean
42 | public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
43 | return http.getSharedObject(AuthenticationManagerBuilder.class)
44 | .authenticationProvider(authProvider())
45 | .build();
46 | }
47 |
48 | @Bean
49 | public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
50 | http
51 | .authorizeHttpRequests(authorizeRequests ->
52 | authorizeRequests
53 | .requestMatchers("/api/**").hasAnyRole(UserAccountType.REST_USER.toString(), UserAccountType.ADMIN_USER.toString())
54 | .requestMatchers("/actuator/**").hasRole(UserAccountType.ADMIN_USER.toString())
55 | )
56 | .httpBasic(Customizer.withDefaults())
57 | .csrf(AbstractHttpConfigurer::disable);
58 |
59 | return http.build();
60 | }
61 | }
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/config/SwaggerConfig.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.config;
2 |
3 | import io.swagger.v3.oas.models.OpenAPI;
4 | import io.swagger.v3.oas.models.info.Contact;
5 | import io.swagger.v3.oas.models.info.Info;
6 | import io.swagger.v3.oas.models.info.License;
7 | import org.springdoc.core.models.GroupedOpenApi;
8 | import org.springframework.context.annotation.Bean;
9 | import org.springframework.context.annotation.Configuration;
10 |
11 |
12 | @Configuration
13 | public class SwaggerConfig {
14 | public static final String BOOKS_API_GROUP = "books-api";
15 |
16 | @Bean
17 | public GroupedOpenApi booksApi() {
18 | return GroupedOpenApi.builder()
19 | .group(BOOKS_API_GROUP)
20 | .packagesToScan("com.edwise.completespring.controllers")
21 | .pathsToMatch("/api/**")
22 | .build();
23 | }
24 |
25 | @Bean
26 | public OpenAPI customOpenAPI() {
27 | return new OpenAPI()
28 | .info(new Info()
29 | .title("Books API")
30 | .description("Your book database!")
31 | .termsOfService("http://en.wikipedia.org/wiki/Terms_of_service")
32 | .contact(new Contact()
33 | .name("edwise")
34 | .url("https://github.com/edwise")
35 | .email("edwise.null@gmail.com"))
36 | .license(new License()
37 | .name("Apache License Version 2.0")
38 | .url("http://www.apache.org/licenses/LICENSE-2.0.html"))
39 | .version("2.0"));
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/controllers/BookController.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.controllers;
2 |
3 | import com.edwise.completespring.assemblers.BookResource;
4 | import com.edwise.completespring.assemblers.BookResourceAssembler;
5 | import com.edwise.completespring.entities.Book;
6 | import com.edwise.completespring.exceptions.InvalidRequestException;
7 | import com.edwise.completespring.services.BookService;
8 | import io.swagger.v3.oas.annotations.Operation;
9 | import io.swagger.v3.oas.annotations.Parameter;
10 | import io.swagger.v3.oas.annotations.media.Content;
11 | import io.swagger.v3.oas.annotations.media.Schema;
12 | import io.swagger.v3.oas.annotations.responses.ApiResponse;
13 | import io.swagger.v3.oas.annotations.responses.ApiResponses;
14 | import io.swagger.v3.oas.annotations.tags.Tag;
15 | import jakarta.validation.Valid;
16 | import lombok.extern.slf4j.Slf4j;
17 | import org.springframework.beans.factory.annotation.Autowired;
18 | import org.springframework.hateoas.Link;
19 | import org.springframework.http.HttpHeaders;
20 | import org.springframework.http.HttpStatus;
21 | import org.springframework.http.MediaType;
22 | import org.springframework.http.ResponseEntity;
23 | import org.springframework.validation.BindingResult;
24 | import org.springframework.web.bind.annotation.PathVariable;
25 | import org.springframework.web.bind.annotation.RequestBody;
26 | import org.springframework.web.bind.annotation.RequestMapping;
27 | import org.springframework.web.bind.annotation.RequestMethod;
28 | import org.springframework.web.bind.annotation.ResponseStatus;
29 | import org.springframework.web.bind.annotation.RestController;
30 |
31 | import java.net.URI;
32 | import java.util.List;
33 | import java.util.Optional;
34 |
35 | @RestController
36 | @RequestMapping("/api/books/")
37 | @Slf4j
38 | @Tag(name = "Books API", description = "Books management API")
39 | public class BookController {
40 |
41 | @Autowired
42 | private BookResourceAssembler bookResourceAssembler;
43 |
44 | @Autowired
45 | private BookService bookService;
46 |
47 | @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
48 | @Operation(summary = "Get Books", description = "Returns all books")
49 | @ApiResponses({
50 | @ApiResponse(responseCode = "200", description = "Exits one book at least", content = @Content(mediaType = "application/json", schema = @Schema(implementation = BookResource.class)))
51 | })
52 | public ResponseEntity> getAll() {
53 | List books = bookService.findAll();
54 | List resourceList = bookResourceAssembler.toModels(books);
55 |
56 | log.info("Books found: {}", books);
57 | return new ResponseEntity<>(resourceList, HttpStatus.OK);
58 | }
59 |
60 | @RequestMapping(method = RequestMethod.GET, value = "{id}", produces = MediaType.APPLICATION_JSON_VALUE)
61 | @Operation(summary = "Get one Book", description = "Returns one book", responses = {
62 | @ApiResponse(responseCode = "200", description = "Exists this book", content = @Content(mediaType = "application/json", schema = @Schema(implementation = BookResource.class)))
63 | })
64 | public ResponseEntity getBook(@Parameter(description = "The id of the book to return") @PathVariable long id) {
65 | Book book = bookService.findOne(id);
66 |
67 | log.info("Book found: {}", book);
68 | return new ResponseEntity<>(bookResourceAssembler.toModel(book), HttpStatus.OK);
69 | }
70 |
71 | @RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE,
72 | consumes = MediaType.APPLICATION_JSON_VALUE)
73 | @Operation(summary = "Create Book", description = "Create a book")
74 | @ApiResponses({
75 | @ApiResponse(responseCode = "201", description = "Successful create of a book")
76 | })
77 | public ResponseEntity createBook(@Valid @RequestBody Book book, BindingResult errors) {
78 | if (errors.hasErrors()) {
79 | throw new InvalidRequestException(errors);
80 | }
81 | Book bookCreated = bookService.create(book);
82 |
83 | log.info("Book created: {}", bookCreated.toString());
84 | return new ResponseEntity<>(createHttpHeadersWithLocation(bookCreated), HttpStatus.CREATED);
85 | }
86 |
87 | @ResponseStatus(HttpStatus.NO_CONTENT)
88 | @RequestMapping(method = RequestMethod.PUT, value = "{id}", produces = MediaType.APPLICATION_JSON_VALUE,
89 | consumes = MediaType.APPLICATION_JSON_VALUE)
90 | @Operation(summary = "Update Book", description = "Update a book")
91 | @ApiResponses({
92 | @ApiResponse(responseCode = "204", description = "Successful update of book")
93 | })
94 | public void updateBook(@Parameter(description = "The id of the book to update") @PathVariable long id,
95 | @Valid @RequestBody Book book, BindingResult errors) {
96 | if (errors.hasErrors()) {
97 | throw new InvalidRequestException(errors);
98 | }
99 | Book dbBook = bookService.findOne(id);
100 | dbBook = bookService.save(dbBook.copyFrom(book));
101 |
102 | log.info("Book updated: {}", dbBook.toString());
103 | }
104 |
105 | @ResponseStatus(HttpStatus.NO_CONTENT)
106 | @RequestMapping(method = RequestMethod.DELETE, value = "{id}", produces = MediaType.APPLICATION_JSON_VALUE)
107 | @Operation(summary = "Delete Book", description = "Delete a book")
108 | @ApiResponses({
109 | @ApiResponse(responseCode = "204", description = "Successful delete of a book")
110 | })
111 | public void deleteBook(@Parameter(description = "The id of the book to delete") @PathVariable long id) {
112 | bookService.delete(id);
113 |
114 | log.info("Book deleted: {}", id);
115 | }
116 |
117 | private HttpHeaders createHttpHeadersWithLocation(Book book) {
118 | HttpHeaders httpHeaders = new HttpHeaders();
119 | Optional selfBookLink = bookResourceAssembler.toModel(book).getLink("self");
120 | httpHeaders.setLocation(URI.create(selfBookLink.map(Link::getHref).orElse("")));
121 | return httpHeaders;
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/controllers/FooController.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.controllers;
2 |
3 | import com.edwise.completespring.assemblers.FooResource;
4 | import com.edwise.completespring.assemblers.FooResourceAssembler;
5 | import com.edwise.completespring.entities.Foo;
6 | import com.edwise.completespring.exceptions.InvalidRequestException;
7 | import io.swagger.v3.oas.annotations.Operation;
8 | import io.swagger.v3.oas.annotations.Parameter;
9 | import io.swagger.v3.oas.annotations.media.Content;
10 | import io.swagger.v3.oas.annotations.media.Schema;
11 | import io.swagger.v3.oas.annotations.responses.ApiResponse;
12 | import io.swagger.v3.oas.annotations.responses.ApiResponses;
13 | import io.swagger.v3.oas.annotations.tags.Tag;
14 | import jakarta.validation.Valid;
15 | import lombok.extern.slf4j.Slf4j;
16 | import org.springframework.beans.factory.annotation.Autowired;
17 | import org.springframework.hateoas.Link;
18 | import org.springframework.http.HttpHeaders;
19 | import org.springframework.http.HttpStatus;
20 | import org.springframework.http.MediaType;
21 | import org.springframework.http.ResponseEntity;
22 | import org.springframework.validation.BindingResult;
23 | import org.springframework.web.bind.annotation.*;
24 |
25 | import java.net.URI;
26 | import java.time.LocalDate;
27 | import java.util.List;
28 | import java.util.Optional;
29 |
30 | @RestController
31 | @RequestMapping(value = "/api/foos/")
32 | @Tag(name = "foos", description = "Foo API")
33 | @Slf4j
34 | public class FooController {
35 | private static final int RESPONSE_CODE_OK = 200;
36 | private static final int RESPONSE_CODE_CREATED = 201;
37 | private static final int RESPONSE_CODE_NO_RESPONSE = 204;
38 | private static final String TEST_ATTRIBUTE_1 = "AttText1";
39 |
40 | @Autowired
41 | private FooResourceAssembler fooResourceAssembler;
42 |
43 | @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
44 | @Operation(summary = "Get Foos", description = "Returns all foos")
45 | @ApiResponses({
46 | @ApiResponse(responseCode = "200", description = "Exits one foo at least", content = @Content(mediaType = "application/json", schema = @Schema(implementation = FooResource.class)))
47 | })
48 | public ResponseEntity> getAll() {
49 | List foos = List.of(
50 | new Foo().setId(1L).setSampleTextAttribute(TEST_ATTRIBUTE_1).setSampleLocalDateAttribute(LocalDate.now()),
51 | new Foo().setId(2L).setSampleTextAttribute(TEST_ATTRIBUTE_1).setSampleLocalDateAttribute(LocalDate.now())
52 | );
53 | List resourceList = fooResourceAssembler.toModels(foos);
54 |
55 | log.info("Foos found: {}", foos);
56 | return new ResponseEntity<>(resourceList, HttpStatus.OK);
57 | }
58 |
59 | @RequestMapping(method = RequestMethod.GET, value = "{id}", produces = MediaType.APPLICATION_JSON_VALUE)
60 | @Operation(summary = "Get one Foo", description = "Returns one foo")
61 | @ApiResponses({
62 | @ApiResponse(responseCode = "200", description = "Exists this foo", content = @Content(mediaType = "application/json", schema = @Schema(implementation = FooResource.class)))
63 | })
64 | public ResponseEntity getFoo(@Parameter(description = "The id of the foo to return")
65 | @PathVariable long id) {
66 | FooResource resource = fooResourceAssembler.toModel(
67 | new Foo().setId(id).setSampleTextAttribute(TEST_ATTRIBUTE_1).setSampleLocalDateAttribute(LocalDate.now())
68 | );
69 |
70 | log.info("Foo found: {}", resource.getFoo());
71 | return new ResponseEntity<>(resource, HttpStatus.OK);
72 | }
73 |
74 | @ResponseStatus(HttpStatus.CREATED)
75 | @RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE,
76 | consumes = MediaType.APPLICATION_JSON_VALUE)
77 | @Operation(summary = "Create Foo", description = "Create a foo")
78 | @ApiResponses({
79 | @ApiResponse(responseCode = "201", description = "Successful create of a foo")
80 | })
81 | public ResponseEntity createFoo(@Valid @RequestBody Foo foo, BindingResult errors) {
82 | if (errors.hasErrors()) {
83 | throw new InvalidRequestException(errors);
84 | }
85 | log.info("Foo created: {}", foo.toString());
86 | return new ResponseEntity<>(createHttpHeadersWithLocation(foo.setId(1L)), HttpStatus.CREATED);
87 | }
88 |
89 | @ResponseStatus(HttpStatus.NO_CONTENT)
90 | @RequestMapping(method = RequestMethod.PUT, value = "{id}", produces = MediaType.APPLICATION_JSON_VALUE,
91 | consumes = MediaType.APPLICATION_JSON_VALUE)
92 | @Operation(summary = "Update Foo", description = "Update a foo")
93 | @ApiResponses({
94 | @ApiResponse(responseCode = "204", description = "Successful update of foo")
95 | })
96 | public void updateFoo(@Parameter(description = "The id of the foo to update")
97 | @PathVariable long id,
98 | @Valid @RequestBody Foo foo, BindingResult errors) {
99 | if (errors.hasErrors()) {
100 | throw new InvalidRequestException(errors);
101 | }
102 | log.info("Foo updated: {}", foo.setId(id).toString());
103 | }
104 |
105 | @ResponseStatus(HttpStatus.NO_CONTENT)
106 | @RequestMapping(method = RequestMethod.DELETE, value = "{id}", produces = MediaType.APPLICATION_JSON_VALUE)
107 | @Operation(summary = "Delete Foo", description = "Delete a foo")
108 | @ApiResponses({
109 | @ApiResponse(responseCode = "204", description = "Successful delete of a foo")
110 | })
111 | public void deleteFoo(@Parameter(description = "The id of the foo to delete")
112 | @PathVariable long id) {
113 |
114 | log.info("Foo deleted: {}", id);
115 | }
116 |
117 | private HttpHeaders createHttpHeadersWithLocation(Foo foo) {
118 | HttpHeaders httpHeaders = new HttpHeaders();
119 | Optional selfBookLink = fooResourceAssembler.toModel(foo).getLink("self");
120 | httpHeaders.setLocation(URI.create(selfBookLink.map(Link::getHref).orElse("")));
121 | return httpHeaders;
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/controllers/RestExceptionProcessor.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.controllers;
2 |
3 | import com.edwise.completespring.exceptions.InvalidRequestException;
4 | import com.edwise.completespring.exceptions.NotFoundException;
5 | import com.edwise.completespring.exceptions.helpers.ErrorInfo;
6 | import lombok.extern.slf4j.Slf4j;
7 | import org.springframework.http.HttpStatus;
8 | import org.springframework.web.bind.annotation.ControllerAdvice;
9 | import org.springframework.web.bind.annotation.ExceptionHandler;
10 | import org.springframework.web.bind.annotation.ResponseBody;
11 | import org.springframework.web.bind.annotation.ResponseStatus;
12 | import org.springframework.web.context.request.WebRequest;
13 |
14 |
15 | @ControllerAdvice
16 | @Slf4j
17 | public class RestExceptionProcessor {
18 | private static final String FIELD_ID = "id";
19 |
20 | @ExceptionHandler(NotFoundException.class)
21 | @ResponseStatus(value = HttpStatus.NOT_FOUND)
22 | @ResponseBody
23 | public ErrorInfo entityNotFound(WebRequest req, NotFoundException ex) {
24 | String errorDescription = req.getDescription(false);
25 |
26 | log.warn("Entity not found: {}", errorDescription);
27 | return new ErrorInfo()
28 | .setUrl(errorDescription)
29 | .addError(FIELD_ID, ex.getMessage());
30 | }
31 |
32 |
33 | @ExceptionHandler(InvalidRequestException.class)
34 | @ResponseStatus(value = HttpStatus.BAD_REQUEST)
35 | @ResponseBody
36 | public ErrorInfo invalidPostData(WebRequest req, InvalidRequestException ex) {
37 | ErrorInfo errors = ex.getErrors();
38 | errors.setUrl(req.getDescription(false));
39 |
40 | log.warn("Invalid request: {}", errors);
41 | return errors;
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/dbutils/DataLoader.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.dbutils;
2 |
3 | import com.edwise.completespring.entities.*;
4 | import com.edwise.completespring.repositories.BookRepository;
5 | import com.edwise.completespring.repositories.SequenceIdRepository;
6 | import com.edwise.completespring.repositories.UserAccountRepository;
7 | import com.edwise.completespring.services.impl.BookServiceImpl;
8 | import lombok.extern.slf4j.Slf4j;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.security.crypto.password.PasswordEncoder;
11 | import org.springframework.stereotype.Component;
12 |
13 | import java.time.LocalDate;
14 | import java.util.List;
15 |
16 | @Slf4j
17 | @Component
18 | public class DataLoader {
19 | private static final String USERACCOUNTS_COLLECTION = "users";
20 | private static final Long BOOKS_INITIAL_SEQUENCE = 4L;
21 | private static final Long USERACCOUNTS_INITIAL_SEQUENCE = 3L;
22 | public static final Long BOOK_ID_1 = 1L;
23 | public static final Long BOOK_ID_2 = 2L;
24 | public static final Long BOOK_ID_3 = 3L;
25 | private static final Long BOOK_ID_4 = 4L;
26 | private static final Long USER_ID_1 = 1L;
27 | private static final Long USER_ID_2 = 2L;
28 | public static final String USER = "user1";
29 | public static final String PASSWORD_USER = "password1";
30 | public static final String ADMIN = "admin";
31 | public static final String PASSWORD_ADMIN = "password1234";
32 |
33 | @Autowired
34 | private BookRepository bookRepository;
35 |
36 | @Autowired
37 | private UserAccountRepository userAccountRepository;
38 |
39 | @Autowired
40 | private SequenceIdRepository sequenceRepository;
41 |
42 | @Autowired
43 | private PasswordEncoder passwordEncoder;
44 |
45 | public void fillDBData() {
46 | log.info("Filling DB data...");
47 | fillDBUsersData();
48 | fillDBBooksData();
49 | log.info("DB filled with data.");
50 | }
51 |
52 | private void fillDBUsersData() {
53 | sequenceRepository.save(new SequenceId()
54 | .setId(USERACCOUNTS_COLLECTION)
55 | .setSeq(USERACCOUNTS_INITIAL_SEQUENCE)
56 | );
57 | userAccountRepository.deleteAll();
58 | List.of(new UserAccount()
59 | .setId(USER_ID_1)
60 | .setUsername(USER)
61 | .setPassword(passwordEncoder.encode(PASSWORD_USER))
62 | .setUserType(UserAccountType.REST_USER),
63 | new UserAccount()
64 | .setId(USER_ID_2)
65 | .setUsername(ADMIN)
66 | .setPassword(passwordEncoder.encode(PASSWORD_ADMIN))
67 | .setUserType(UserAccountType.ADMIN_USER))
68 | .forEach(userAccountRepository::save);
69 | }
70 |
71 | private void fillDBBooksData() {
72 | sequenceRepository.save(new SequenceId()
73 | .setId(BookServiceImpl.BOOK_COLLECTION)
74 | .setSeq(BOOKS_INITIAL_SEQUENCE)
75 | );
76 | bookRepository.deleteAll();
77 | List.of(new Book()
78 | .setId(BOOK_ID_1)
79 | .setTitle("Libro prueba mongo")
80 | .setAuthors(List.of(new Author().setName("Edu").setSurname("Antón")))
81 | .setIsbn("11-333-12")
82 | .setReleaseDate(LocalDate.now())
83 | .setPublisher(new Publisher().setName("Editorial 1").setCountry("ES").setOnline(false)),
84 | new Book()
85 | .setId(BOOK_ID_2)
86 | .setTitle("Libro prueba mongo 2")
87 | .setAuthors(
88 | List.of(new Author().setName("Otro").setSurname("Más"),
89 | new Author().setName("S.").setSurname("King")))
90 | .setIsbn("12-1234-12")
91 | .setReleaseDate(LocalDate.now())
92 | .setPublisher(new Publisher().setName("Editorial 4").setCountry("UK").setOnline(true)),
93 | new Book()
94 | .setId(BOOK_ID_3)
95 | .setTitle("Libro prueba mongo 3")
96 | .setAuthors(List.of(new Author().setName("Nadie").setSurname("Nobody")))
97 | .setIsbn("12-9999-92")
98 | .setReleaseDate(LocalDate.now())
99 | .setPublisher(new Publisher().setName("Editorial 7").setCountry("ES").setOnline(true)),
100 | new Book()
101 | .setId(BOOK_ID_4)
102 | .setTitle("Libro prueba mongo 4")
103 | .setAuthors(List.of(new Author().setName("Perry").setSurname("Mason")))
104 | .setIsbn("22-34565-12")
105 | .setReleaseDate(LocalDate.now())
106 | .setPublisher(new Publisher().setName("Editorial 33").setCountry("US").setOnline(true)))
107 | .forEach(bookRepository::save);
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/entities/Author.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.entities;
2 |
3 | import io.swagger.annotations.ApiModel;
4 | import io.swagger.annotations.ApiModelProperty;
5 | import jakarta.validation.constraints.NotEmpty;
6 | import lombok.Data;
7 | import lombok.EqualsAndHashCode;
8 | import lombok.ToString;
9 | import lombok.experimental.Accessors;
10 |
11 | @ApiModel(value = "Author entity", description = "Complete info of a entity author")
12 | @Data
13 | @Accessors(chain = true)
14 | @EqualsAndHashCode(doNotUseGetters = true)
15 | @ToString(doNotUseGetters = true)
16 | public class Author {
17 |
18 | @ApiModelProperty(value = "The name of the author", required = true)
19 | @NotEmpty
20 | private String name;
21 |
22 | @ApiModelProperty(value = "The surname of the author")
23 | @NotEmpty
24 | private String surname;
25 |
26 | public Author copyFrom(Author other) {
27 | this.name = other.name;
28 | this.surname = other.surname;
29 |
30 | return this;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/entities/Book.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.entities;
2 |
3 | import io.swagger.annotations.ApiModel;
4 | import io.swagger.annotations.ApiModelProperty;
5 | import jakarta.validation.Valid;
6 | import jakarta.validation.constraints.NotEmpty;
7 | import jakarta.validation.constraints.NotNull;
8 | import lombok.Data;
9 | import lombok.EqualsAndHashCode;
10 | import lombok.ToString;
11 | import lombok.experimental.Accessors;
12 | import org.springframework.data.annotation.Id;
13 | import org.springframework.data.mongodb.core.mapping.Document;
14 |
15 | import java.time.LocalDate;
16 | import java.util.ArrayList;
17 | import java.util.List;
18 | import java.util.stream.Collectors;
19 |
20 | @Document(collection = "books")
21 | @ApiModel(value = "Book entity", description = "Complete info of a entity book")
22 | @Data
23 | @Accessors(chain = true)
24 | @EqualsAndHashCode(exclude = {"id"}, doNotUseGetters = true)
25 | @ToString(doNotUseGetters = true)
26 | public class Book {
27 |
28 | @ApiModelProperty(value = "The id of the book")
29 | @Id
30 | private Long id;
31 |
32 | @ApiModelProperty(value = "The title of the book", required = true)
33 | @NotEmpty
34 | private String title;
35 |
36 | @ApiModelProperty(value = "The authors of the book", required = true)
37 | @Valid
38 | private List authors;
39 |
40 | @ApiModelProperty(value = "The isbn of the book", required = true)
41 | @NotEmpty
42 | private String isbn;
43 |
44 | @ApiModelProperty(value = "The release date of the book", required = true, dataType = "LocalDate")
45 | @NotNull
46 | private LocalDate releaseDate;
47 |
48 | @ApiModelProperty(value = "The publisher of the book", required = true)
49 | @Valid
50 | @NotNull
51 | private Publisher publisher;
52 |
53 | public Book copyFrom(Book other) {
54 | this.title = other.title;
55 | if (other.authors != null) {
56 | this.authors = new ArrayList<>();
57 | this.authors.addAll(other.authors.stream().map(new Author()::copyFrom).collect(Collectors.toList()));
58 | }
59 | this.isbn = other.isbn;
60 | this.releaseDate = other.releaseDate;
61 | if (other.publisher != null) {
62 | this.publisher = new Publisher().copyFrom(other.publisher);
63 | }
64 |
65 | return this;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/entities/Foo.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.entities;
2 |
3 | import io.swagger.annotations.ApiModel;
4 | import io.swagger.annotations.ApiModelProperty;
5 | import jakarta.validation.constraints.NotNull;
6 | import lombok.Data;
7 | import lombok.EqualsAndHashCode;
8 | import lombok.ToString;
9 | import lombok.experimental.Accessors;
10 |
11 | import java.time.LocalDate;
12 |
13 | @ApiModel(value = "Foo entity", description = "Complete info of a entity foo")
14 | @Data
15 | @Accessors(chain = true)
16 | @EqualsAndHashCode(exclude = {"id"}, doNotUseGetters = true)
17 | @ToString(doNotUseGetters = true)
18 | public class Foo {
19 |
20 | @ApiModelProperty(value = "Sample Id Attribute", required = true)
21 | private Long id;
22 |
23 | @ApiModelProperty(value = "Sample Text Attribute", required = true)
24 | @NotNull
25 | private String sampleTextAttribute;
26 |
27 | @ApiModelProperty(value = "Sample Local Date Attribute", required = true, dataType = "LocalDate")
28 | @NotNull
29 | private LocalDate sampleLocalDateAttribute;
30 |
31 | public Foo copyFrom(Foo other) {
32 | this.sampleTextAttribute = other.sampleTextAttribute;
33 | this.sampleLocalDateAttribute = other.sampleLocalDateAttribute;
34 |
35 | return this;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/entities/Publisher.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.entities;
2 |
3 | import io.swagger.annotations.ApiModel;
4 | import io.swagger.annotations.ApiModelProperty;
5 | import jakarta.validation.constraints.NotEmpty;
6 | import lombok.Data;
7 | import lombok.EqualsAndHashCode;
8 | import lombok.ToString;
9 | import lombok.experimental.Accessors;
10 |
11 | @ApiModel(value = "Publisher entity", description = "Complete info of a entity publisher")
12 | @Data
13 | @Accessors(chain = true)
14 | @EqualsAndHashCode(doNotUseGetters = true)
15 | @ToString(doNotUseGetters = true)
16 | public class Publisher {
17 |
18 | @ApiModelProperty(value = "The name of the publisher", required = true)
19 | @NotEmpty
20 | private String name;
21 |
22 | @ApiModelProperty(value = "The country of the publisher", required = true)
23 | private String country;
24 |
25 | @ApiModelProperty(value = "If the publisher is online or not", required = true)
26 | private boolean isOnline;
27 |
28 | public Publisher copyFrom(Publisher other) {
29 | this.name = other.name;
30 | this.country = other.country;
31 | this.isOnline = other.isOnline;
32 |
33 | return this;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/entities/SequenceId.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.entities;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import lombok.ToString;
6 | import lombok.experimental.Accessors;
7 | import org.springframework.data.annotation.Id;
8 | import org.springframework.data.mongodb.core.mapping.Document;
9 |
10 | @Document(collection = "sequences")
11 | @Setter @Getter
12 | @Accessors(chain = true)
13 | @ToString(doNotUseGetters = true)
14 | public class SequenceId {
15 | @Id
16 | private String id;
17 |
18 | private long seq;
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/entities/UserAccount.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.entities;
2 |
3 | import lombok.Data;
4 | import lombok.EqualsAndHashCode;
5 | import lombok.NonNull;
6 | import lombok.ToString;
7 | import lombok.experimental.Accessors;
8 | import org.springframework.data.annotation.Id;
9 | import org.springframework.data.mongodb.core.mapping.Document;
10 |
11 | @Document(collection = "users")
12 | @Data
13 | @Accessors(chain = true)
14 | @EqualsAndHashCode(exclude = {"id"}, doNotUseGetters = true)
15 | @ToString(doNotUseGetters = true)
16 | public class UserAccount {
17 |
18 | @Id
19 | private Long id;
20 |
21 | private String username;
22 | private String password;
23 |
24 | @NonNull
25 | private UserAccountType userType = UserAccountType.REST_USER;
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/entities/UserAccountType.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.entities;
2 |
3 | public enum UserAccountType {
4 | REST_USER,
5 | ADMIN_USER
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/exceptions/InvalidRequestException.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.exceptions;
2 |
3 | import com.edwise.completespring.exceptions.helpers.ErrorInfo;
4 | import lombok.Getter;
5 | import org.springframework.validation.BindingResult;
6 |
7 | public class InvalidRequestException extends RuntimeException {
8 |
9 | @Getter
10 | private final ErrorInfo errors;
11 |
12 | public InvalidRequestException(BindingResult bindingResultErrors) {
13 | super(bindingResultErrors.toString());
14 | this.errors = ErrorInfo.generateErrorInfoFromBindingResult(bindingResultErrors);
15 | }
16 |
17 | }
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/exceptions/NotFoundException.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.exceptions;
2 |
3 | public class NotFoundException extends RuntimeException {
4 |
5 | public NotFoundException(String msg) {
6 | super(msg);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/exceptions/SequenceException.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.exceptions;
2 |
3 | public class SequenceException extends RuntimeException {
4 |
5 | public SequenceException(String msg) {
6 | super(msg);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/exceptions/helpers/ErrorInfo.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.exceptions.helpers;
2 |
3 | import lombok.EqualsAndHashCode;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 | import lombok.ToString;
7 | import lombok.experimental.Accessors;
8 | import org.springframework.validation.BindingResult;
9 |
10 | import java.io.Serializable;
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | @Accessors(chain = true)
15 | @EqualsAndHashCode(doNotUseGetters = true)
16 | @ToString(doNotUseGetters = true)
17 | public class ErrorInfo implements Serializable {
18 |
19 | private static final long serialVersionUID = 673514291953827696L;
20 |
21 | @Getter
22 | @Setter
23 | private String url;
24 |
25 | @Getter
26 | private List errors = new ArrayList<>();
27 |
28 | public ErrorInfo addError(String field, String message) {
29 | errors.add(new ErrorItem().setField(field).setMessage(message));
30 | return this;
31 | }
32 |
33 | public static ErrorInfo generateErrorInfoFromBindingResult(BindingResult errors) {
34 | ErrorInfo errorInfo = new ErrorInfo();
35 | errors.getFieldErrors()
36 | .forEach(fieldError -> errorInfo.addError(fieldError.getField(), fieldError.getDefaultMessage()));
37 |
38 | return errorInfo;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/exceptions/helpers/ErrorItem.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.exceptions.helpers;
2 |
3 | import lombok.Data;
4 | import lombok.EqualsAndHashCode;
5 | import lombok.ToString;
6 | import lombok.experimental.Accessors;
7 |
8 | import java.io.Serializable;
9 |
10 | @Data
11 | @Accessors(chain = true)
12 | @EqualsAndHashCode(doNotUseGetters = true)
13 | @ToString(doNotUseGetters = true)
14 | class ErrorItem implements Serializable {
15 | private static final long serialVersionUID = 582572111791708597L;
16 |
17 | private String field;
18 | private String message;
19 | }
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/repositories/BookRepository.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.repositories;
2 |
3 | import com.edwise.completespring.entities.Book;
4 | import org.springframework.data.mongodb.repository.MongoRepository;
5 |
6 | import java.time.LocalDate;
7 | import java.util.List;
8 |
9 | public interface BookRepository extends MongoRepository {
10 |
11 | List findByTitle(String title);
12 | List findByReleaseDate(LocalDate releaseDate);
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/repositories/SequenceIdRepository.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.repositories;
2 |
3 | import com.edwise.completespring.entities.SequenceId;
4 |
5 | public interface SequenceIdRepository {
6 | void save(SequenceId sequenceId);
7 |
8 | long getNextSequenceId(String key);
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/repositories/UserAccountRepository.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.repositories;
2 |
3 | import com.edwise.completespring.entities.UserAccount;
4 | import org.springframework.data.mongodb.repository.MongoRepository;
5 |
6 | public interface UserAccountRepository extends MongoRepository {
7 |
8 | UserAccount findByUsername(String username);
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/repositories/impl/SequenceIdRepositoryImpl.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.repositories.impl;
2 |
3 | import com.edwise.completespring.entities.SequenceId;
4 | import com.edwise.completespring.exceptions.SequenceException;
5 | import com.edwise.completespring.repositories.SequenceIdRepository;
6 | import lombok.extern.slf4j.Slf4j;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.data.mongodb.core.FindAndModifyOptions;
9 | import org.springframework.data.mongodb.core.MongoOperations;
10 | import org.springframework.data.mongodb.core.query.Criteria;
11 | import org.springframework.data.mongodb.core.query.Query;
12 | import org.springframework.data.mongodb.core.query.Update;
13 | import org.springframework.stereotype.Repository;
14 |
15 | @Repository
16 | @Slf4j
17 | public class SequenceIdRepositoryImpl implements SequenceIdRepository {
18 |
19 | @Autowired
20 | private MongoOperations mongoOperation;
21 |
22 | @Override
23 | public long getNextSequenceId(String key) {
24 | //get sequence id
25 | Query query = new Query(Criteria.where("_id").is(key));
26 |
27 | //increase sequence id by 1
28 | Update update = new Update();
29 | update.inc("seq", 1);
30 |
31 | //return new increased id
32 | FindAndModifyOptions options = new FindAndModifyOptions();
33 | options.returnNew(true);
34 |
35 | SequenceId seqId =
36 | mongoOperation.findAndModify(query, update, options, SequenceId.class);
37 |
38 | //if no id, throws SequenceException
39 | if (seqId == null) {
40 | log.error("Unable to get sequence id for key: {}", key);
41 | throw new SequenceException("Unable to get sequence id for key: " + key);
42 | }
43 |
44 | log.debug("Next sequendId: {}", seqId);
45 | return seqId.getSeq();
46 | }
47 |
48 | @Override
49 | public void save(SequenceId sequenceId) {
50 | log.debug("New sequenceId: {}", sequenceId);
51 | mongoOperation.save(sequenceId);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/services/BookService.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.services;
2 |
3 | import com.edwise.completespring.entities.Book;
4 |
5 | import java.time.LocalDate;
6 | import java.util.List;
7 |
8 | public interface BookService extends Service {
9 |
10 | List findByTitle(String title);
11 |
12 | List findByReleaseDate(LocalDate releaseDate);
13 |
14 | Book create(Book book);
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/services/Service.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.services;
2 |
3 | import java.util.List;
4 |
5 | public interface Service {
6 |
7 | List findAll();
8 | T save(T entity);
9 | T findOne(I id);
10 | void delete(I id);
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/edwise/completespring/services/impl/BookServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.services.impl;
2 |
3 | import com.edwise.completespring.entities.Book;
4 | import com.edwise.completespring.exceptions.NotFoundException;
5 | import com.edwise.completespring.repositories.BookRepository;
6 | import com.edwise.completespring.repositories.SequenceIdRepository;
7 | import com.edwise.completespring.services.BookService;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.stereotype.Service;
10 |
11 | import java.time.LocalDate;
12 | import java.util.List;
13 | import java.util.Optional;
14 |
15 | @Service
16 | public class BookServiceImpl implements BookService {
17 | public static final String BOOK_COLLECTION = "books";
18 | private static final String BOOK_NOT_FOUND_MSG = "Book not found";
19 |
20 | @Autowired
21 | private BookRepository bookRepository;
22 |
23 | @Autowired
24 | private SequenceIdRepository sequenceIdRepository;
25 |
26 | @Override
27 | public List findAll() {
28 | return bookRepository.findAll();
29 | }
30 |
31 | @Override
32 | public Book save(Book book) {
33 | return bookRepository.save(book);
34 | }
35 |
36 | @Override
37 | public Book findOne(Long id) {
38 | Optional result = bookRepository.findById(id);
39 | if (!result.isPresent()) {
40 | throw new NotFoundException(BOOK_NOT_FOUND_MSG);
41 | }
42 | return result.get();
43 | }
44 |
45 | @Override
46 | public void delete(Long id) {
47 | bookRepository.deleteById(id);
48 | }
49 |
50 | @Override
51 | public Book create(Book book) {
52 | Long id = sequenceIdRepository.getNextSequenceId(BOOK_COLLECTION);
53 | book.setId(id);
54 |
55 | return bookRepository.save(book);
56 | }
57 |
58 | @Override
59 | public List findByTitle(String title) {
60 | return bookRepository.findByTitle(title);
61 | }
62 |
63 | @Override
64 | public List findByReleaseDate(LocalDate releaseDate) {
65 | return bookRepository.findByReleaseDate(releaseDate);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | spring.jackson.serialization.write_dates_as_timestamps=false
2 |
3 | spring.data.mongodb.host=localhost
4 | spring.data.mongodb.port=27017
5 | spring.data.mongodb.database=springBootDB
6 |
7 | db.resetAndLoadOnStartup=true
8 |
--------------------------------------------------------------------------------
/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n
10 |
11 |
12 |
13 |
14 | logs/csp.log
15 |
16 | %d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
17 |
18 |
19 |
20 | logs/csp_%d{yyyy-MM-dd}.%i.log
21 |
22 | 10MB
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/test/java/com/edwise/completespring/ApplicationForServerTest.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.junit.jupiter.api.extension.ExtendWith;
5 | import org.mockito.junit.jupiter.MockitoExtension;
6 | import org.springframework.boot.builder.SpringApplicationBuilder;
7 |
8 | import static org.mockito.Mockito.mock;
9 | import static org.mockito.Mockito.verify;
10 |
11 | @ExtendWith(MockitoExtension.class)
12 | public class ApplicationForServerTest {
13 |
14 | @Test
15 | public void testConfigureForServer() {
16 | SpringApplicationBuilder springApplicationBuilder = mock(SpringApplicationBuilder.class);
17 | ApplicationForServer applicationForServer = new ApplicationForServer();
18 |
19 | applicationForServer.configure(springApplicationBuilder);
20 |
21 | verify(springApplicationBuilder).sources(Application.class);
22 | }
23 | }
--------------------------------------------------------------------------------
/src/test/java/com/edwise/completespring/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring;
2 |
3 | import com.edwise.completespring.dbutils.DataLoader;
4 | import org.junit.jupiter.api.Test;
5 | import org.junit.jupiter.api.extension.ExtendWith;
6 | import org.mockito.InjectMocks;
7 | import org.mockito.Mock;
8 | import org.mockito.junit.jupiter.MockitoExtension;
9 |
10 | import static org.mockito.Mockito.verify;
11 | import static org.mockito.Mockito.verifyNoInteractions;
12 |
13 | @ExtendWith(MockitoExtension.class)
14 | public class ApplicationTest {
15 |
16 | @Mock
17 | private DataLoader dataLoader;
18 |
19 | @InjectMocks
20 | private Application application;
21 |
22 | @Test
23 | public void testInitAppLoadingDBData() {
24 | application.initApp(true);
25 |
26 | verify(dataLoader).fillDBData();
27 | }
28 |
29 | @Test
30 | public void testInitAppWithoutLoadDBData() {
31 | application.initApp(false);
32 |
33 | verifyNoInteractions(dataLoader);
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/test/java/com/edwise/completespring/assemblers/BookResourceAssemblerTest.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.assemblers;
2 |
3 | import com.edwise.completespring.entities.Book;
4 | import org.junit.jupiter.api.Test;
5 | import org.junit.jupiter.api.extension.ExtendWith;
6 | import org.mockito.Mock;
7 | import org.mockito.junit.jupiter.MockitoExtension;
8 | import org.springframework.mock.web.MockHttpServletRequest;
9 | import org.springframework.web.context.request.RequestContextHolder;
10 | import org.springframework.web.context.request.ServletRequestAttributes;
11 |
12 | import static org.assertj.core.api.Assertions.assertThat;
13 | import static org.mockito.Mockito.when;
14 |
15 | @ExtendWith(MockitoExtension.class)
16 | public class BookResourceAssemblerTest {
17 | private static final long BOOK_ID_TEST = 1234L;
18 |
19 | @Mock
20 | private Book book;
21 |
22 | private BookResourceAssembler bookResourceAssembler = new BookResourceAssembler();
23 |
24 | @Test
25 | public void testInstantiateResource() {
26 | BookResource bookResource = bookResourceAssembler.instantiateModel(book);
27 |
28 | assertThat(bookResource).isNotNull();
29 | assertThat(bookResource.getBook()).isEqualTo(book);
30 | }
31 |
32 | @Test
33 | public void testToResource() {
34 | RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(new MockHttpServletRequest()));
35 | when(book.getId()).thenReturn(BOOK_ID_TEST);
36 |
37 | BookResource bookResource = bookResourceAssembler.toModel(book);
38 |
39 | assertThat(bookResource).isNotNull();
40 | assertThat(bookResource.getBook()).isEqualTo(book);
41 | }
42 | }
--------------------------------------------------------------------------------
/src/test/java/com/edwise/completespring/assemblers/FooResourceAssemblerTest.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.assemblers;
2 |
3 | import com.edwise.completespring.entities.Foo;
4 | import org.junit.jupiter.api.Test;
5 | import org.junit.jupiter.api.extension.ExtendWith;
6 | import org.mockito.Mock;
7 | import org.mockito.junit.jupiter.MockitoExtension;
8 | import org.springframework.mock.web.MockHttpServletRequest;
9 | import org.springframework.web.context.request.RequestContextHolder;
10 | import org.springframework.web.context.request.ServletRequestAttributes;
11 |
12 | import static org.assertj.core.api.Assertions.assertThat;
13 | import static org.mockito.Mockito.when;
14 |
15 | @ExtendWith(MockitoExtension.class)
16 | public class FooResourceAssemblerTest {
17 | private static final long FOO_ID_TEST = 1234L;
18 |
19 | @Mock
20 | private Foo foo;
21 |
22 | private FooResourceAssembler fooResourceAssembler = new FooResourceAssembler();
23 |
24 | @Test
25 | public void testInstantiateResource() {
26 | FooResource fooResource = fooResourceAssembler.instantiateModel(foo);
27 |
28 | assertThat(fooResource).isNotNull();
29 | assertThat(fooResource.getFoo()).isEqualTo(foo);
30 | }
31 |
32 | @Test
33 | public void testToResource() {
34 | RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(new MockHttpServletRequest()));
35 | when(foo.getId()).thenReturn(FOO_ID_TEST);
36 |
37 | FooResource fooResource = fooResourceAssembler.toModel(foo);
38 |
39 | assertThat(fooResource).isNotNull();
40 | assertThat(fooResource.getFoo()).isEqualTo(foo);
41 | }
42 | }
--------------------------------------------------------------------------------
/src/test/java/com/edwise/completespring/controllers/BookControllerTest.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.controllers;
2 |
3 | import com.edwise.completespring.assemblers.BookResource;
4 | import com.edwise.completespring.assemblers.BookResourceAssembler;
5 | import com.edwise.completespring.entities.*;
6 | import com.edwise.completespring.exceptions.InvalidRequestException;
7 | import com.edwise.completespring.exceptions.NotFoundException;
8 | import com.edwise.completespring.services.BookService;
9 | import com.edwise.completespring.testutil.BookBuilder;
10 | import org.junit.jupiter.api.BeforeEach;
11 | import org.junit.jupiter.api.Test;
12 | import org.junit.jupiter.api.extension.ExtendWith;
13 | import org.mockito.InjectMocks;
14 | import org.mockito.Mock;
15 | import org.mockito.junit.jupiter.MockitoExtension;
16 | import org.springframework.hateoas.Link;
17 | import org.springframework.http.HttpStatus;
18 | import org.springframework.http.ResponseEntity;
19 | import org.springframework.mock.web.MockHttpServletRequest;
20 | import org.springframework.validation.BindingResult;
21 | import org.springframework.web.context.request.RequestContextHolder;
22 | import org.springframework.web.context.request.ServletRequestAttributes;
23 |
24 | import java.time.LocalDate;
25 | import java.util.ArrayList;
26 | import java.util.List;
27 |
28 | import static org.assertj.core.api.Assertions.assertThat;
29 | import static org.assertj.core.api.Assertions.catchThrowable;
30 | import static org.mockito.Mockito.*;
31 |
32 | @ExtendWith(MockitoExtension.class)
33 | public class BookControllerTest {
34 | private static final long BOOK_ID_TEST1 = 1L;
35 | private static final String BOOK_TITLE_TEST1 = "Lord of the Rings";
36 | private static final String BOOK_TITLE_TEST2 = "Hamlet";
37 | private static final long BOOK_ID_TEST2 = 1000L;
38 | private static final LocalDate BOOK_RELEASEDATE_TEST1 = LocalDate.of(2013, 1, 26);
39 | private static final LocalDate BOOK_RELEASEDATE_TEST2 = LocalDate.of(2011, 11, 16);
40 | private static final String BOOK_ISBN_TEST1 = "11-333-12";
41 | private static final String BOOK_ISBN_TEST2 = "11-666-77";
42 | private static final String PUBLISHER_NAME_TEST1 = "Planeta";
43 | private static final String PUBLISHER_NAME_TEST2 = "Gigamesh";
44 | private static final String PUBLISHER_COUNTRY_TEST1 = "ES";
45 | private static final String PUBLISHER_COUNTRY_TEST2 = "US";
46 | private static final String AUTHOR_NAME_TEST1 = "J.R.R.";
47 | private static final String AUTHOR_NAME_TEST2 = "William";
48 | private static final String AUTHOR_SURNAME_TEST1 = "Tolkien";
49 | private static final String AUTHOR_SURNAME_TEST2 = "Shakespeare";
50 | private static final int ONE_TIME = 1;
51 |
52 | @Mock
53 | private BookService bookService;
54 |
55 | @Mock
56 | private BindingResult errors;
57 |
58 | @Mock
59 | private BookResourceAssembler bookResourceAssembler;
60 |
61 | @InjectMocks
62 | private BookController controller = new BookController();
63 |
64 | @BeforeEach
65 | public void setUp() {
66 | RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(new MockHttpServletRequest()));
67 | }
68 |
69 | @Test
70 | public void testCreate() {
71 | Book bookReq = new BookBuilder()
72 | .title(BOOK_TITLE_TEST1)
73 | .authors(List.of(new Author().setName(AUTHOR_NAME_TEST1).setSurname(AUTHOR_SURNAME_TEST1)))
74 | .isbn(BOOK_ISBN_TEST1)
75 | .releaseDate(BOOK_RELEASEDATE_TEST1)
76 | .publisher(new Publisher().setName(PUBLISHER_NAME_TEST1).setCountry(PUBLISHER_COUNTRY_TEST1).setOnline(false))
77 | .build();
78 | Book bookResp = new Book().copyFrom(bookReq).setId(BOOK_ID_TEST1);
79 | when(errors.hasErrors()).thenReturn(false);
80 | when(bookService.create(bookReq)).thenReturn(bookResp);
81 | when(bookResourceAssembler.toModel(any(Book.class))).thenReturn(createBookResourceWithLink(bookResp));
82 |
83 | ResponseEntity result = controller.createBook(bookReq, errors);
84 |
85 | assertThat(result.getBody()).isNull();
86 | assertThat(result.getStatusCode()).isEqualTo(HttpStatus.CREATED);
87 | assertThat(result.getHeaders().getLocation().toString()).contains("/api/books/" + BOOK_ID_TEST1);
88 | verify(bookResourceAssembler, times(ONE_TIME)).toModel(bookResp);
89 | verify(errors, times(ONE_TIME)).hasErrors();
90 | verify(bookService, times(ONE_TIME)).create(bookReq);
91 | }
92 |
93 | @Test
94 | public void testCreateInvalidRequest() {
95 | Book bookReq = new Book();
96 | when(errors.hasErrors()).thenReturn(true);
97 |
98 | Throwable thrown = catchThrowable(() -> controller.createBook(bookReq, errors));
99 |
100 | assertThat(thrown).isInstanceOf(InvalidRequestException.class);
101 | }
102 |
103 | @Test
104 | public void testUpdate() {
105 | Book bookReq = new BookBuilder()
106 | .title(BOOK_TITLE_TEST1)
107 | .authors(List.of(new Author().setName(AUTHOR_NAME_TEST1).setSurname(AUTHOR_SURNAME_TEST1)))
108 | .isbn(BOOK_ISBN_TEST1)
109 | .releaseDate(BOOK_RELEASEDATE_TEST1)
110 | .publisher(new Publisher().setName(PUBLISHER_NAME_TEST1).setCountry(PUBLISHER_COUNTRY_TEST1).setOnline(false))
111 | .build();
112 | Book bookDB = new BookBuilder()
113 | .id(BOOK_ID_TEST1)
114 | .title(BOOK_TITLE_TEST2)
115 | .authors(List.of(new Author().setName(AUTHOR_NAME_TEST1).setSurname(AUTHOR_SURNAME_TEST1)))
116 | .isbn(BOOK_ISBN_TEST1)
117 | .releaseDate(BOOK_RELEASEDATE_TEST1)
118 | .publisher(new Publisher().setName(PUBLISHER_NAME_TEST1).setCountry(PUBLISHER_COUNTRY_TEST1).setOnline(false))
119 | .build();
120 | when(bookService.findOne(BOOK_ID_TEST1)).thenReturn(bookDB);
121 | when(bookService.save(bookDB.copyFrom(bookReq))).thenReturn(bookDB.copyFrom(bookReq));
122 |
123 | controller.updateBook(BOOK_ID_TEST1, bookReq, errors);
124 |
125 | verify(bookService, times(ONE_TIME)).findOne(BOOK_ID_TEST1);
126 | verify(bookService, times(ONE_TIME)).save(bookDB.copyFrom(bookReq));
127 | }
128 |
129 | @Test
130 | public void testUpdateInvalidRequest() {
131 | Book bookReq = new BookBuilder()
132 | .title(BOOK_TITLE_TEST1)
133 | .isbn(BOOK_ISBN_TEST1)
134 | .releaseDate(BOOK_RELEASEDATE_TEST1)
135 | .build();
136 | when(errors.hasErrors()).thenReturn(true);
137 |
138 | Throwable thrown = catchThrowable(() -> controller.updateBook(BOOK_ID_TEST1, bookReq, errors));
139 |
140 | assertThat(thrown).isInstanceOf(InvalidRequestException.class);
141 | }
142 |
143 | @Test
144 | public void testGet() {
145 | Book bookReq = new BookBuilder()
146 | .id(BOOK_ID_TEST1)
147 | .title(BOOK_TITLE_TEST1)
148 | .authors(List.of(new Author().setName(AUTHOR_NAME_TEST1).setSurname(AUTHOR_SURNAME_TEST1)))
149 | .isbn(BOOK_ISBN_TEST1)
150 | .releaseDate(BOOK_RELEASEDATE_TEST1)
151 | .publisher(new Publisher().setName(PUBLISHER_NAME_TEST1).setCountry(PUBLISHER_COUNTRY_TEST1).setOnline(false))
152 | .build();
153 | when(bookService.findOne(BOOK_ID_TEST1)).thenReturn(bookReq);
154 | when(bookResourceAssembler.toModel(any(Book.class))).thenReturn(new BookResource().setBook(bookReq));
155 |
156 | ResponseEntity result = controller.getBook(BOOK_ID_TEST1);
157 |
158 | assertThat(result.getBody()).isNotNull();
159 | assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
160 | verify(bookService, times(ONE_TIME)).findOne(BOOK_ID_TEST1);
161 | verify(bookResourceAssembler, times(ONE_TIME)).toModel(bookReq);
162 | }
163 |
164 |
165 | @Test
166 | public void testGetNotFound() {
167 | when(bookService.findOne(BOOK_ID_TEST2)).thenThrow(new NotFoundException("Book not exist"));
168 |
169 | Throwable thrown = catchThrowable(() -> controller.getBook(BOOK_ID_TEST2));
170 |
171 | assertThat(thrown).isInstanceOf(NotFoundException.class);
172 | }
173 |
174 | @Test
175 | public void testUpdateNotFound() {
176 | Book bookReq = new BookBuilder()
177 | .title(BOOK_TITLE_TEST1)
178 | .authors(List.of(new Author().setName(AUTHOR_NAME_TEST1).setSurname(AUTHOR_SURNAME_TEST1)))
179 | .isbn(BOOK_ISBN_TEST1)
180 | .releaseDate(BOOK_RELEASEDATE_TEST1)
181 | .publisher(new Publisher().setName(PUBLISHER_NAME_TEST1).setCountry(PUBLISHER_COUNTRY_TEST1).setOnline(false))
182 | .build();
183 | when(bookService.findOne(BOOK_ID_TEST1)).thenThrow(new NotFoundException("Book not exist"));
184 |
185 | Throwable thrown = catchThrowable(() -> controller.updateBook(BOOK_ID_TEST1, bookReq, errors));
186 |
187 | assertThat(thrown).isInstanceOf(NotFoundException.class);
188 | }
189 |
190 | @Test
191 | public void testDelete() {
192 | doNothing().when(bookService).delete(BOOK_ID_TEST1);
193 |
194 | controller.deleteBook(BOOK_ID_TEST1);
195 |
196 | verify(bookService, times(ONE_TIME)).delete(BOOK_ID_TEST1);
197 | }
198 |
199 | @Test
200 | public void testFindAll() {
201 | List books = createTestBookList();
202 | when(bookService.findAll()).thenReturn(books);
203 | when(bookResourceAssembler.toModels(anyList())).thenReturn(new ArrayList<>());
204 |
205 | ResponseEntity> result = controller.getAll();
206 |
207 | assertThat(result.getBody()).isNotNull();
208 | assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
209 | verify(bookService, times(ONE_TIME)).findAll();
210 | verify(bookResourceAssembler, times(ONE_TIME)).toModels(books);
211 | }
212 |
213 | private BookResource createBookResourceWithLink(Book book) {
214 | BookResource bookResource = new BookResource().setBook(book);
215 | bookResource.add(Link.of("http://localhost:8080/api/books/" + BOOK_ID_TEST1));
216 | return bookResource;
217 | }
218 |
219 | private List createTestBookList() {
220 | Book book1 = new BookBuilder()
221 | .id(BOOK_ID_TEST1)
222 | .title(BOOK_TITLE_TEST1)
223 | .authors(List.of(AuthorTest.createAuthor(AUTHOR_NAME_TEST1, AUTHOR_SURNAME_TEST1)))
224 | .isbn(BOOK_ISBN_TEST1)
225 | .releaseDate(BOOK_RELEASEDATE_TEST1)
226 | .publisher(PublisherTest.createPublisher(PUBLISHER_NAME_TEST1, PUBLISHER_COUNTRY_TEST1, false))
227 | .build();
228 | Book book2 = new BookBuilder()
229 | .id(BOOK_ID_TEST2)
230 | .title(BOOK_TITLE_TEST2)
231 | .authors(List.of(AuthorTest.createAuthor(AUTHOR_NAME_TEST2, AUTHOR_SURNAME_TEST2)))
232 | .isbn(BOOK_ISBN_TEST2)
233 | .releaseDate(BOOK_RELEASEDATE_TEST2)
234 | .publisher(PublisherTest.createPublisher(PUBLISHER_NAME_TEST2, PUBLISHER_COUNTRY_TEST2, true))
235 | .build();
236 |
237 | return List.of(book1, book2);
238 | }
239 | }
240 |
--------------------------------------------------------------------------------
/src/test/java/com/edwise/completespring/controllers/FooControllerTest.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.controllers;
2 |
3 | import com.edwise.completespring.assemblers.FooResource;
4 | import com.edwise.completespring.assemblers.FooResourceAssembler;
5 | import com.edwise.completespring.entities.Foo;
6 | import com.edwise.completespring.entities.FooTest;
7 | import com.edwise.completespring.exceptions.InvalidRequestException;
8 | import org.junit.jupiter.api.BeforeEach;
9 | import org.junit.jupiter.api.Test;
10 | import org.junit.jupiter.api.extension.ExtendWith;
11 | import org.mockito.InjectMocks;
12 | import org.mockito.Mock;
13 | import org.mockito.junit.jupiter.MockitoExtension;
14 | import org.springframework.hateoas.Link;
15 | import org.springframework.http.HttpStatus;
16 | import org.springframework.http.ResponseEntity;
17 | import org.springframework.mock.web.MockHttpServletRequest;
18 | import org.springframework.validation.BindingResult;
19 | import org.springframework.web.context.request.RequestContextHolder;
20 | import org.springframework.web.context.request.ServletRequestAttributes;
21 |
22 | import java.time.LocalDate;
23 | import java.util.ArrayList;
24 | import java.util.List;
25 |
26 | import static org.assertj.core.api.Assertions.assertThat;
27 | import static org.assertj.core.api.Assertions.catchThrowable;
28 | import static org.mockito.Mockito.*;
29 |
30 | @ExtendWith(MockitoExtension.class)
31 | public class FooControllerTest {
32 | private static final long FOO_ID_TEST1 = 1L;
33 | private static final String FOO_TEXT_ATTR_TEST1 = "AttText1";
34 | private static final LocalDate DATE_TEST1 = LocalDate.of(2013, 1, 26);
35 |
36 | @Mock
37 | BindingResult errors;
38 |
39 | @Mock
40 | FooResourceAssembler fooResourceAssembler;
41 |
42 | @InjectMocks
43 | private FooController controller = new FooController();
44 |
45 | @BeforeEach
46 | public void setUp() {
47 | RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(new MockHttpServletRequest()));
48 | }
49 |
50 | @Test
51 | public void testUpdateInvalidRequest() {
52 | Foo fooReq = FooTest.createFoo(FOO_ID_TEST1, null, null);
53 | when(errors.hasErrors()).thenReturn(true);
54 |
55 | Throwable thrown = catchThrowable(() -> controller.updateFoo(FOO_ID_TEST1, fooReq, errors));
56 |
57 | assertThat(thrown).isInstanceOf(InvalidRequestException.class);
58 | }
59 |
60 |
61 | @Test
62 | public void testCreate() {
63 | Foo fooReq = FooTest.createFoo(null, FOO_TEXT_ATTR_TEST1, DATE_TEST1);
64 | Foo fooCreated = FooTest.createFoo(FOO_ID_TEST1, FOO_TEXT_ATTR_TEST1, DATE_TEST1);
65 | when(errors.hasErrors()).thenReturn(false);
66 | when(fooResourceAssembler.toModel(any(Foo.class))).thenReturn(createFooResourceWithLink(fooCreated));
67 |
68 | ResponseEntity result = controller.createFoo(fooReq, errors);
69 |
70 | assertThat(result.getBody()).isNull();
71 | assertThat(result.getStatusCode()).isEqualTo(HttpStatus.CREATED);
72 | assertThat(result.getHeaders().getLocation().toString()).contains("/api/foos/" + FOO_ID_TEST1);
73 | verify(fooResourceAssembler, times(1)).toModel(fooCreated);
74 | verify(errors, times(1)).hasErrors();
75 | }
76 |
77 | @Test
78 | public void testCreateInvalidRequest() {
79 | Foo FooReq = FooTest.createFoo(null, null, null);
80 | when(errors.hasErrors()).thenReturn(true);
81 |
82 | Throwable thrown = catchThrowable(() -> controller.createFoo(FooReq, errors));
83 |
84 | assertThat(thrown).isInstanceOf(InvalidRequestException.class);
85 | }
86 |
87 | @Test
88 | public void testUpdate() {
89 | Foo FooReq = FooTest.createFoo(FOO_ID_TEST1, FOO_TEXT_ATTR_TEST1, DATE_TEST1);
90 |
91 | controller.updateFoo(FOO_ID_TEST1, FooReq, errors);
92 | }
93 |
94 | @Test
95 | public void testGet() {
96 | when(fooResourceAssembler.toModel(any(Foo.class)))
97 | .thenReturn(new FooResource().setFoo(FooTest.createFoo(FOO_ID_TEST1, null, null)));
98 |
99 | ResponseEntity result = controller.getFoo(FOO_ID_TEST1);
100 |
101 | assertThat(result.getBody()).isNotNull();
102 | }
103 |
104 |
105 | @Test
106 | public void testDelete() {
107 | controller.deleteFoo(FOO_ID_TEST1);
108 | }
109 |
110 | @Test
111 | public void testFindAll() {
112 | when(fooResourceAssembler.toModels(anyList())).thenReturn(new ArrayList<>());
113 |
114 | ResponseEntity> result = controller.getAll();
115 |
116 | assertThat(result.getBody()).isNotNull();
117 | }
118 |
119 | private FooResource createFooResourceWithLink(Foo foo) {
120 | FooResource fooResource = new FooResource().setFoo(foo);
121 | fooResource.add(Link.of("http://localhost:8080/api/foos/" + FOO_ID_TEST1));
122 | return fooResource;
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/test/java/com/edwise/completespring/controllers/RestExceptionProcessorTest.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.controllers;
2 |
3 | import com.edwise.completespring.exceptions.InvalidRequestException;
4 | import com.edwise.completespring.exceptions.NotFoundException;
5 | import com.edwise.completespring.exceptions.helpers.ErrorInfo;
6 | import org.junit.jupiter.api.BeforeEach;
7 | import org.junit.jupiter.api.Test;
8 | import org.junit.jupiter.api.extension.ExtendWith;
9 | import org.mockito.Mock;
10 | import org.mockito.junit.jupiter.MockitoExtension;
11 | import org.springframework.mock.web.MockHttpServletRequest;
12 | import org.springframework.validation.BindingResult;
13 | import org.springframework.validation.FieldError;
14 | import org.springframework.web.context.request.RequestContextHolder;
15 | import org.springframework.web.context.request.ServletRequestAttributes;
16 | import org.springframework.web.context.request.ServletWebRequest;
17 | import org.springframework.web.context.request.WebRequest;
18 |
19 | import java.util.List;
20 |
21 | import static org.assertj.core.api.Assertions.assertThat;
22 | import static org.mockito.Mockito.when;
23 |
24 | @ExtendWith(MockitoExtension.class)
25 | public class RestExceptionProcessorTest {
26 | private static final int ONE_ITEM = 1;
27 | private static final int TWO_ITEMS = 2;
28 | private static final String FIELD_ERROR_OBJECT_TEST1 = "Book";
29 | private static final String FIELD_ERROR_FIELD_TEST1 = "title";
30 | private static final String FIELD_ERROR_FIELD_TEST2 = "isbn";
31 | private static final String FIELD_ERROR_MESSAGE_TEST1 = "No puede ser nulo";
32 | private static final String FIELD_ERROR_MESSAGE_TEST2 = "No puede ser vacio";
33 | private static final String EXCEPTION_MESSAGE_TEST1 = "No existe la entidad";
34 |
35 | private RestExceptionProcessor restExceptionProcessor;
36 |
37 | private WebRequest request;
38 |
39 | @Mock
40 | private BindingResult errors;
41 |
42 | @BeforeEach
43 | public void setUp() {
44 | MockHttpServletRequest mockRequest = new MockHttpServletRequest();
45 | this.request = new ServletWebRequest(mockRequest);
46 | RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(mockRequest));
47 | this.restExceptionProcessor = new RestExceptionProcessor();
48 | }
49 |
50 | @Test
51 | public void testEntityNotFound() {
52 | NotFoundException exception = new NotFoundException(EXCEPTION_MESSAGE_TEST1);
53 |
54 | ErrorInfo errorInfo = restExceptionProcessor.entityNotFound(request, exception);
55 |
56 | assertThat(errorInfo).isNotNull();
57 | assertThat(errorInfo.getUrl()).isEqualTo(request.getDescription(false));
58 | assertThat(errorInfo.getErrors()).hasSize(ONE_ITEM);
59 | }
60 |
61 | @Test
62 | public void testInvalidPostData() {
63 | when(errors.getFieldErrors()).thenReturn(createMockListFieldErrors());
64 | InvalidRequestException exception = new InvalidRequestException(errors);
65 |
66 | ErrorInfo errorInfo = restExceptionProcessor.invalidPostData(request, exception);
67 |
68 | assertThat(errorInfo).isNotNull();
69 | assertThat(errorInfo.getUrl()).isEqualTo(request.getDescription(false));
70 | assertThat(errorInfo.getErrors()).hasSize(TWO_ITEMS);
71 | }
72 |
73 | private List createMockListFieldErrors() {
74 | return List.of(
75 | new FieldError(FIELD_ERROR_OBJECT_TEST1, FIELD_ERROR_FIELD_TEST1, FIELD_ERROR_MESSAGE_TEST1),
76 | new FieldError(FIELD_ERROR_OBJECT_TEST1, FIELD_ERROR_FIELD_TEST2, FIELD_ERROR_MESSAGE_TEST2));
77 | }
78 |
79 | }
--------------------------------------------------------------------------------
/src/test/java/com/edwise/completespring/dbutils/DataLoaderTest.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.dbutils;
2 |
3 | import com.edwise.completespring.entities.Book;
4 | import com.edwise.completespring.entities.SequenceId;
5 | import com.edwise.completespring.entities.UserAccount;
6 | import com.edwise.completespring.repositories.BookRepository;
7 | import com.edwise.completespring.repositories.SequenceIdRepository;
8 | import com.edwise.completespring.repositories.UserAccountRepository;
9 | import org.junit.jupiter.api.Test;
10 | import org.junit.jupiter.api.extension.ExtendWith;
11 | import org.mockito.InjectMocks;
12 | import org.mockito.Mock;
13 | import org.mockito.junit.jupiter.MockitoExtension;
14 | import org.springframework.security.crypto.password.PasswordEncoder;
15 |
16 | import static org.mockito.ArgumentMatchers.any;
17 | import static org.mockito.Mockito.times;
18 | import static org.mockito.Mockito.verify;
19 |
20 | @ExtendWith(MockitoExtension.class)
21 | public class DataLoaderTest {
22 | private static final int TWO_TIMES = 2;
23 | private static final int FOUR_TIMES = 4;
24 |
25 | @Mock
26 | private BookRepository bookRepository;
27 |
28 | @Mock
29 | private UserAccountRepository userAccountRepository;
30 |
31 | @Mock
32 | private SequenceIdRepository sequenceRepository;
33 |
34 | @Mock
35 | private PasswordEncoder passwordEncoder;
36 |
37 | @InjectMocks
38 | private DataLoader dataLoader;
39 |
40 | @Test
41 | public void testFillDBData() {
42 | dataLoader.fillDBData();
43 |
44 | verifyRepositoriesCalls();
45 | }
46 |
47 | private void verifyRepositoriesCalls() {
48 | verify(sequenceRepository, times(TWO_TIMES)).save(any(SequenceId.class));
49 | verify(bookRepository).deleteAll();
50 | verify(bookRepository, times(FOUR_TIMES)).save(any(Book.class));
51 | verify(userAccountRepository).deleteAll();
52 | verify(passwordEncoder, times(TWO_TIMES)).encode(any());
53 | verify(userAccountRepository, times(TWO_TIMES)).save(any(UserAccount.class));
54 | }
55 | }
--------------------------------------------------------------------------------
/src/test/java/com/edwise/completespring/entities/AuthorTest.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.entities;
2 |
3 |
4 | import org.junit.jupiter.api.Test;
5 |
6 | import static org.assertj.core.api.Assertions.assertThat;
7 |
8 | public class AuthorTest {
9 | private static final String NAME_TEST1 = "J.";
10 | private static final String NAME_TEST2 = "Stephen";
11 | private static final String SURNAME_TEST1 = "Tolkien";
12 | private static final String SURNAME_TEST2 = "King";
13 |
14 | @Test
15 | public void testCopyFrom() {
16 | Author authorFrom = createAuthor(NAME_TEST1, SURNAME_TEST1);
17 | Author author = createAuthor(null, null);
18 |
19 | author.copyFrom(authorFrom);
20 |
21 | assertThat(author).isEqualTo(authorFrom);
22 | }
23 |
24 | @Test
25 | public void testEquals() {
26 | Author author1 = createAuthor(NAME_TEST1, SURNAME_TEST1);
27 | Author author2 = createAuthor(NAME_TEST1, SURNAME_TEST1);
28 |
29 | assertThat(author1.equals(author2) && author2.equals(author1)).isTrue();
30 | }
31 |
32 | @Test
33 | public void testNotEqualsWithDifferentsFields() {
34 | Author author1 = createAuthor(NAME_TEST1, SURNAME_TEST1);
35 | Author author2 = createAuthor(NAME_TEST1, SURNAME_TEST2);
36 |
37 | assertThat(author1.equals(author2) || author2.equals(author1)).isFalse();
38 | }
39 |
40 | @Test
41 | public void testNotEqualsWithDifferentsObjects() {
42 | Author author = createAuthor(NAME_TEST1, null);
43 |
44 | assertThat(author).isNotEqualTo(new Object());
45 | }
46 |
47 | @Test
48 | public void testHashCode() {
49 | Author author1 = createAuthor(NAME_TEST1, SURNAME_TEST1);
50 | Author author2 = createAuthor(NAME_TEST1, SURNAME_TEST1);
51 |
52 | assertThat(author1.hashCode()).isEqualTo(author2.hashCode());
53 | }
54 |
55 | @Test
56 | public void testHasCodeWithDifferentFields() {
57 | Author author1 = createAuthor(NAME_TEST1, SURNAME_TEST1);
58 | Author author2 = createAuthor(NAME_TEST2, SURNAME_TEST2);
59 |
60 | assertThat(author1.hashCode()).isNotEqualTo(author2.hashCode());
61 | }
62 |
63 | @Test
64 | public void testToString() {
65 | Author author = createAuthor(null, null);
66 |
67 | assertThatAuthorStringContainsAllFields(author.toString());
68 | }
69 |
70 | private void assertThatAuthorStringContainsAllFields(String authorString) {
71 | assertThat(authorString).contains("name=null");
72 | assertThat(authorString).contains("surname=null");
73 | }
74 |
75 | public static Author createAuthor(String name, String surname) {
76 | return new Author()
77 | .setName(name)
78 | .setSurname(surname);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/test/java/com/edwise/completespring/entities/BookTest.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.entities;
2 |
3 | import com.edwise.completespring.testutil.BookBuilder;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import java.time.LocalDate;
7 | import java.util.List;
8 |
9 | import static org.assertj.core.api.Assertions.assertThat;
10 |
11 | public class BookTest {
12 |
13 | private static final long BOOK_ID_TEST1 = 31L;
14 | private static final String BOOK_TITLE_TEST1 = "Lord of the Rings";
15 | private static final String BOOK_TITLE_TEST2 = "Bautismo de Fuego";
16 | private static final String AUTHOR_NAME_TEST1 = "J.R.R.";
17 | private static final String AUTHOR_SURNAME_TEST1 = "Tolkien";
18 | private static final String BOOK_ISBN_TEST1 = "11-333-12";
19 | private static final String BOOK_ISBN_TEST2 = "21-929-34";
20 | private static final String PUBLISHER_NAME_TEST1 = "Editorial Planeta";
21 | private static final String PUBLISHER_NAME_TEST2 = "Gigamesh";
22 | private static final String PUBLISHER_COUNTRY_TEST1 = "ES";
23 | private static final LocalDate DATE_TEST1 = LocalDate.of(2013, 1, 26);
24 |
25 | @Test
26 | public void testCopyFrom() {
27 | Book bookFrom = new BookBuilder()
28 | .id(BOOK_ID_TEST1)
29 | .title(BOOK_TITLE_TEST1)
30 | .authors(List.of(AuthorTest.createAuthor(AUTHOR_NAME_TEST1, AUTHOR_SURNAME_TEST1)))
31 | .isbn(BOOK_ISBN_TEST1)
32 | .releaseDate(DATE_TEST1)
33 | .publisher(PublisherTest.createPublisher(PUBLISHER_NAME_TEST1, PUBLISHER_COUNTRY_TEST1, true))
34 | .build();
35 | Book bookTo = new BookBuilder().build();
36 |
37 | bookTo.copyFrom(bookFrom);
38 |
39 | assertThat(bookTo).isEqualTo(bookFrom);
40 | }
41 |
42 | @Test
43 | public void testEquals() {
44 | Book book1 = new BookBuilder()
45 | .id(BOOK_ID_TEST1)
46 | .title(BOOK_TITLE_TEST1)
47 | .authors(List.of(AuthorTest.createAuthor(AUTHOR_NAME_TEST1, AUTHOR_SURNAME_TEST1)))
48 | .isbn(BOOK_ISBN_TEST1)
49 | .releaseDate(DATE_TEST1)
50 | .publisher(PublisherTest.createPublisher(PUBLISHER_NAME_TEST1, PUBLISHER_COUNTRY_TEST1, true))
51 | .build();
52 | Book book2 = new BookBuilder()
53 | .id(BOOK_ID_TEST1)
54 | .title(BOOK_TITLE_TEST1)
55 | .authors(List.of(AuthorTest.createAuthor(AUTHOR_NAME_TEST1, AUTHOR_SURNAME_TEST1)))
56 | .isbn(BOOK_ISBN_TEST1)
57 | .releaseDate(DATE_TEST1)
58 | .publisher(PublisherTest.createPublisher(PUBLISHER_NAME_TEST1, PUBLISHER_COUNTRY_TEST1, true))
59 | .build();
60 |
61 | assertThat(book1.equals(book2) && book2.equals(book1)).isTrue();
62 | }
63 |
64 | @Test
65 | public void testNotEqualsWithDifferentsFields() {
66 | Book book1 = new BookBuilder()
67 | .id(BOOK_ID_TEST1)
68 | .title(BOOK_TITLE_TEST1)
69 | .authors(List.of(AuthorTest.createAuthor(AUTHOR_NAME_TEST1, AUTHOR_SURNAME_TEST1)))
70 | .isbn(BOOK_ISBN_TEST1)
71 | .releaseDate(DATE_TEST1)
72 | .publisher(PublisherTest.createPublisher(PUBLISHER_NAME_TEST1, PUBLISHER_COUNTRY_TEST1, true))
73 | .build();
74 | Book book2 = new BookBuilder()
75 | .id(BOOK_ID_TEST1)
76 | .title(BOOK_TITLE_TEST2)
77 | .authors(List.of(AuthorTest.createAuthor(AUTHOR_NAME_TEST1, AUTHOR_SURNAME_TEST1)))
78 | .isbn(BOOK_ISBN_TEST2)
79 | .releaseDate(DATE_TEST1)
80 | .publisher(PublisherTest.createPublisher(PUBLISHER_NAME_TEST2, PUBLISHER_COUNTRY_TEST1, false))
81 | .build();
82 |
83 | assertThat(book1.equals(book2) || book2.equals(book1)).isFalse();
84 | }
85 |
86 | @Test
87 | public void testNotEqualsWithDifferentsObjects() {
88 | Book book = new BookBuilder().id(BOOK_ID_TEST1).build();
89 |
90 | assertThat(book).isNotEqualTo(new Object());
91 | }
92 |
93 | @Test
94 | public void testHashCode() {
95 | Book book1 = new BookBuilder()
96 | .id(BOOK_ID_TEST1)
97 | .title(BOOK_TITLE_TEST1)
98 | .authors(List.of(AuthorTest.createAuthor(AUTHOR_NAME_TEST1, AUTHOR_SURNAME_TEST1)))
99 | .isbn(BOOK_ISBN_TEST1)
100 | .releaseDate(DATE_TEST1)
101 | .publisher(PublisherTest.createPublisher(PUBLISHER_NAME_TEST1, PUBLISHER_COUNTRY_TEST1, true))
102 | .build();
103 | Book book2 = new BookBuilder()
104 | .id(BOOK_ID_TEST1)
105 | .title(BOOK_TITLE_TEST1)
106 | .authors(List.of(AuthorTest.createAuthor(AUTHOR_NAME_TEST1, AUTHOR_SURNAME_TEST1)))
107 | .isbn(BOOK_ISBN_TEST1)
108 | .releaseDate(DATE_TEST1)
109 | .publisher(PublisherTest.createPublisher(PUBLISHER_NAME_TEST1, PUBLISHER_COUNTRY_TEST1, true))
110 | .build();
111 |
112 | assertThat(book1.hashCode()).isEqualTo(book2.hashCode());
113 | }
114 |
115 | @Test
116 | public void testHasCodeWithDifferentFields() {
117 | Book book1 = new BookBuilder()
118 | .id(BOOK_ID_TEST1)
119 | .title(BOOK_TITLE_TEST1)
120 | .authors(List.of(AuthorTest.createAuthor(AUTHOR_NAME_TEST1, AUTHOR_SURNAME_TEST1)))
121 | .isbn(BOOK_ISBN_TEST1)
122 | .releaseDate(DATE_TEST1)
123 | .publisher(PublisherTest.createPublisher(PUBLISHER_NAME_TEST1, PUBLISHER_COUNTRY_TEST1, true))
124 | .build();
125 | Book book2 = new BookBuilder()
126 | .id(BOOK_ID_TEST1)
127 | .title(BOOK_TITLE_TEST2)
128 | .authors(List.of(AuthorTest.createAuthor(AUTHOR_NAME_TEST1, AUTHOR_SURNAME_TEST1)))
129 | .isbn(BOOK_ISBN_TEST2)
130 | .releaseDate(DATE_TEST1)
131 | .publisher(PublisherTest.createPublisher(PUBLISHER_NAME_TEST2, PUBLISHER_COUNTRY_TEST1, false))
132 | .build();
133 |
134 | assertThat(book1.hashCode()).isNotEqualTo(book2.hashCode());
135 | }
136 |
137 | @Test
138 | public void testToString() {
139 | Book book = new BookBuilder().build();
140 |
141 | assertThatBookStringContainsAllFields(book.toString());
142 | }
143 |
144 | private void assertThatBookStringContainsAllFields(String bookString) {
145 | assertThat(bookString).contains("id=null");
146 | assertThat(bookString).contains("title=null");
147 | assertThat(bookString).contains("authors=null");
148 | assertThat(bookString).contains("isbn=null");
149 | assertThat(bookString).contains("releaseDate=null");
150 | assertThat(bookString).contains("publisher=null");
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/test/java/com/edwise/completespring/entities/FooTest.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.entities;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import java.time.LocalDate;
6 |
7 | import static org.assertj.core.api.Assertions.assertThat;
8 |
9 | public class FooTest {
10 | private static final long ID_TEST1 = 123L;
11 | private static final long ID_TEST2 = 456L;
12 | private static final String TEXT_ATTR_TEST1 = "AttText1";
13 | private static final String TEXT_ATTR_TEST2 = "AttText2";
14 | private static final LocalDate DATE_TEST1 = LocalDate.of(2013, 1, 26);
15 |
16 | @Test
17 | public void testCopyFrom() {
18 | Foo fooFrom = createFoo(ID_TEST1, TEXT_ATTR_TEST1, DATE_TEST1);
19 | Foo foo = createFoo(ID_TEST2, null, null);
20 |
21 | foo.copyFrom(fooFrom);
22 |
23 | assertThat(foo).isEqualTo(fooFrom);
24 | }
25 |
26 | @Test
27 | public void testEquals() {
28 | Foo foo1 = createFoo(ID_TEST1, TEXT_ATTR_TEST1, DATE_TEST1);
29 | Foo foo2 = createFoo(ID_TEST1, TEXT_ATTR_TEST1, DATE_TEST1);
30 |
31 | assertThat(foo1.equals(foo2) && foo2.equals(foo1)).isTrue();
32 | }
33 |
34 | @Test
35 | public void testNotEqualsWithDifferentsFields() {
36 | Foo foo1 = createFoo(ID_TEST1, TEXT_ATTR_TEST1, null);
37 | Foo foo2 = createFoo(ID_TEST1, TEXT_ATTR_TEST2, null);
38 |
39 | assertThat(foo1.equals(foo2) || foo2.equals(foo1)).isFalse();
40 | }
41 |
42 | @Test
43 | public void testNotEqualsWithDifferentsObjects() {
44 | Foo foo = createFoo(ID_TEST1, null, null);
45 |
46 | assertThat(foo).isNotEqualTo(new Object());
47 | }
48 |
49 | @Test
50 | public void testHashCode() {
51 | Foo foo1 = createFoo(ID_TEST1, TEXT_ATTR_TEST1, DATE_TEST1);
52 | Foo foo2 = createFoo(ID_TEST1, TEXT_ATTR_TEST1, DATE_TEST1);
53 |
54 | assertThat(foo1.hashCode()).isEqualTo(foo2.hashCode());
55 | }
56 |
57 | @Test
58 | public void testHasCodeWithDifferentFields() {
59 | Foo foo1 = createFoo(ID_TEST1, TEXT_ATTR_TEST1, DATE_TEST1);
60 | Foo foo2 = createFoo(ID_TEST2, TEXT_ATTR_TEST2, DATE_TEST1);
61 |
62 | assertThat(foo1.hashCode()).isNotEqualTo(foo2.hashCode());
63 | }
64 |
65 | @Test
66 | public void testToString() {
67 | Foo foo = createFoo(null, null, null);
68 |
69 | assertThatFooStringContainsAllFields(foo.toString());
70 | }
71 |
72 | private void assertThatFooStringContainsAllFields(String fooString) {
73 | assertThat(fooString).contains("id=null");
74 | assertThat(fooString).contains("sampleTextAttribute=null");
75 | assertThat(fooString).contains("sampleLocalDateAttribute=null");
76 | }
77 |
78 | public static Foo createFoo(Long id, String textAttribute, LocalDate localDateAttribute) {
79 | return new Foo()
80 | .setId(id)
81 | .setSampleTextAttribute(textAttribute)
82 | .setSampleLocalDateAttribute(localDateAttribute);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/test/java/com/edwise/completespring/entities/PublisherTest.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.entities;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import static org.assertj.core.api.Assertions.assertThat;
6 |
7 | public class PublisherTest {
8 | private static final String NAME_TEST1 = "Alfaguara";
9 | private static final String NAME_TEST2 = "Gigamesh";
10 | private static final String COUNTRY_TEST1 = "ES";
11 | private static final String COUNTRY_TEST2 = "US";
12 |
13 | @Test
14 | public void testCopyFrom() {
15 | Publisher publisherFrom = createPublisher(NAME_TEST1, COUNTRY_TEST1, true);
16 | Publisher publisher = createPublisher(null, null, false);
17 |
18 | publisher.copyFrom(publisherFrom);
19 |
20 | assertThat(publisher).isEqualTo(publisherFrom);
21 | }
22 |
23 | @Test
24 | public void testEquals() {
25 | Publisher publisher1 = createPublisher(NAME_TEST1, COUNTRY_TEST1, true);
26 | Publisher publisher2 = createPublisher(NAME_TEST1, COUNTRY_TEST1, true);
27 |
28 | assertThat(publisher1.equals(publisher2) && publisher2.equals(publisher1)).isTrue();
29 | }
30 |
31 | @Test
32 | public void testNotEqualsWithDifferentsFields() {
33 | Publisher publisher1 = createPublisher(NAME_TEST1, COUNTRY_TEST1, true);
34 | Publisher publisher2 = createPublisher(NAME_TEST1, COUNTRY_TEST2, false);
35 |
36 | assertThat(publisher1.equals(publisher2) || publisher2.equals(publisher1)).isFalse();
37 | }
38 |
39 | @Test
40 | public void testNotEqualsWithDifferentsObjects() {
41 | Publisher publisher = createPublisher(NAME_TEST1, COUNTRY_TEST1, false);
42 |
43 | assertThat(publisher).isNotEqualTo(new Object());
44 | }
45 |
46 | @Test
47 | public void testHashCode() {
48 | Publisher publisher1 = createPublisher(NAME_TEST1, COUNTRY_TEST1, true);
49 | Publisher publisher2 = createPublisher(NAME_TEST1, COUNTRY_TEST1, true);
50 |
51 | assertThat(publisher1.hashCode()).isEqualTo(publisher2.hashCode());
52 | }
53 |
54 | @Test
55 | public void testHasCodeWithDifferentFields() {
56 | Publisher publisher1 = createPublisher(NAME_TEST1, COUNTRY_TEST1, true);
57 | Publisher publisher2 = createPublisher(NAME_TEST2, COUNTRY_TEST2, false);
58 |
59 | assertThat(publisher1.hashCode()).isNotEqualTo(publisher2.hashCode());
60 | }
61 |
62 | @Test
63 | public void testToString() {
64 | Publisher publisher = createPublisher(null, null, false);
65 |
66 | assertThatPublisherStringContainsAllFields(publisher.toString());
67 | }
68 |
69 | private void assertThatPublisherStringContainsAllFields(String publisherString) {
70 | assertThat(publisherString).contains("name=null");
71 | assertThat(publisherString).contains("country=null");
72 | assertThat(publisherString).contains("isOnline=false");
73 | }
74 |
75 | public static Publisher createPublisher(String name, String country, boolean isOnline) {
76 | return new Publisher()
77 | .setName(name)
78 | .setCountry(country)
79 | .setOnline(isOnline);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/test/java/com/edwise/completespring/entities/UserAccountTest.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.entities;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import static org.assertj.core.api.Assertions.assertThat;
6 |
7 | public class UserAccountTest {
8 | private static final long ID_TEST1 = 123L;
9 | private static final long ID_TEST2 = 456L;
10 | private static final String USERNAME_TEST1 = "aragorn1981";
11 | private static final String USERNAME_TEST2 = "neo_80";
12 | private static final String PASSWORD_TEST1 = "passwordTest1";
13 | private static final UserAccountType USER_TYPE_TEST1 = UserAccountType.REST_USER;
14 |
15 | @Test
16 | public void testEquals() {
17 | UserAccount userAccount1 = createUserAccount(ID_TEST1, USERNAME_TEST1, PASSWORD_TEST1, USER_TYPE_TEST1);
18 | UserAccount userAccount2 = createUserAccount(ID_TEST1, USERNAME_TEST1, PASSWORD_TEST1, USER_TYPE_TEST1);
19 |
20 | assertThat(userAccount1.equals(userAccount2) && userAccount2.equals(userAccount1)).isTrue();
21 | }
22 |
23 | @Test
24 | public void testNotEqualsWithDifferentsFields() {
25 | UserAccount userAccount1 = createUserAccount(ID_TEST1, USERNAME_TEST1, null, USER_TYPE_TEST1);
26 | UserAccount userAccount2 = createUserAccount(ID_TEST1, USERNAME_TEST2, null, USER_TYPE_TEST1);
27 |
28 | assertThat(userAccount1.equals(userAccount2) || userAccount2.equals(userAccount1)).isFalse();
29 | }
30 |
31 | @Test
32 | public void testNotEqualsWithDifferentsObjects() {
33 | UserAccount userAccount = createUserAccount(ID_TEST1, null, null, USER_TYPE_TEST1);
34 |
35 | assertThat(userAccount).isNotEqualTo(new Object());
36 | }
37 |
38 | @Test
39 | public void testHashCode() {
40 | UserAccount userAccount1 = createUserAccount(ID_TEST1, USERNAME_TEST1, PASSWORD_TEST1, USER_TYPE_TEST1);
41 | UserAccount userAccount2 = createUserAccount(ID_TEST1, USERNAME_TEST1, PASSWORD_TEST1, USER_TYPE_TEST1);
42 |
43 | assertThat(userAccount1.hashCode()).isEqualTo(userAccount2.hashCode());
44 | }
45 |
46 | @Test
47 | public void testHasCodeWithDifferentFields() {
48 | UserAccount userAccount1 = createUserAccount(ID_TEST1, USERNAME_TEST1, PASSWORD_TEST1, USER_TYPE_TEST1);
49 | UserAccount userAccount2 = createUserAccount(ID_TEST2, USERNAME_TEST2, PASSWORD_TEST1, USER_TYPE_TEST1);
50 |
51 | assertThat(userAccount1.hashCode()).isNotEqualTo(userAccount2.hashCode());
52 | }
53 |
54 | @Test
55 | public void testToString() {
56 | UserAccount userAccount = createUserAccount(null, null, null, USER_TYPE_TEST1);
57 |
58 | assertThatUserAccountStringContainsAllFields(userAccount.toString());
59 | }
60 |
61 | private void assertThatUserAccountStringContainsAllFields(String userAccountString) {
62 | assertThat(userAccountString).contains("id=null");
63 | assertThat(userAccountString).contains("username=null");
64 | assertThat(userAccountString).contains("password=null");
65 | }
66 |
67 | private static UserAccount createUserAccount(Long id, String username, String password, UserAccountType userAccountType) {
68 | return new UserAccount()
69 | .setId(id)
70 | .setUsername(username)
71 | .setPassword(password)
72 | .setUserType(userAccountType);
73 | }
74 | }
--------------------------------------------------------------------------------
/src/test/java/com/edwise/completespring/exceptions/helpers/ErrorInfoTest.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.exceptions.helpers;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.springframework.validation.BindingResult;
5 | import org.springframework.validation.FieldError;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | import static org.assertj.core.api.Assertions.assertThat;
11 | import static org.mockito.Mockito.*;
12 |
13 | public class ErrorInfoTest {
14 | private static final int TWO_ITEMS = 2;
15 | private static final int THREE_ITEMS = 3;
16 | private static final String FIELD_TEST1 = "Field1";
17 | private static final String FIELD_TEST2 = "Field2";
18 | private static final String MESSAGE_TEST1 = "MessageText";
19 | private static final String MESSAGE_TEST2 = "MessageText2";
20 | private static final String URL_TEST1 = "URL_test";
21 | private static final String TITLE_FIELD = "title";
22 | private static final String ISBN_FIELD = "isbn";
23 | private static final String RELEASE_DATE_FIELD = "releaseDate";
24 | private static final String IT_CANT_BE_NULL_MSG = "No puede ser nulo";
25 | private static final String IT_CANT_BE_EMPTY_MSG = "No puede ser vacio";
26 | private static final String BOOK_OBJECT_NAME = "Book";
27 |
28 | @Test
29 | public void testAddError() {
30 | ErrorInfo errorInfo = createErrorInfo(null).addError(FIELD_TEST1, MESSAGE_TEST1).addError(FIELD_TEST2,
31 | MESSAGE_TEST2);
32 |
33 | assertThat(errorInfo.getErrors()).isNotNull();
34 | assertThat(errorInfo.getErrors()).hasSize(TWO_ITEMS);
35 | }
36 |
37 | @Test
38 | public void testGenerateErrorInfoFromBindingResult() {
39 | BindingResult bindingResultMock = mock(BindingResult.class);
40 | when(bindingResultMock.getFieldErrors()).thenReturn(createMockListFieldErrors());
41 |
42 | ErrorInfo errorInfo = ErrorInfo.generateErrorInfoFromBindingResult(bindingResultMock);
43 |
44 | assertThat(errorInfo).isNotNull();
45 | assertThat(errorInfo.getErrors()).hasSize(THREE_ITEMS);
46 | assertThat(errorInfo.getErrors()).containsExactlyInAnyOrder(
47 | new ErrorItem().setField(TITLE_FIELD).setMessage(IT_CANT_BE_NULL_MSG),
48 | new ErrorItem().setField(ISBN_FIELD).setMessage(IT_CANT_BE_EMPTY_MSG),
49 | new ErrorItem().setField(RELEASE_DATE_FIELD).setMessage(IT_CANT_BE_NULL_MSG));
50 | verify(bindingResultMock).getFieldErrors();
51 | }
52 |
53 | @Test
54 | public void testGenerateErrorInfoFromBindingResultEmpty() {
55 | BindingResult bindingResultMock = mock(BindingResult.class);
56 | when(bindingResultMock.getFieldErrors()).thenReturn(new ArrayList<>());
57 |
58 | ErrorInfo errorInfo = ErrorInfo.generateErrorInfoFromBindingResult(bindingResultMock);
59 |
60 | assertThat(errorInfo).isNotNull();
61 | assertThat(errorInfo.getErrors()).hasSize(0);
62 | verify(bindingResultMock).getFieldErrors();
63 | }
64 |
65 | private List createMockListFieldErrors() {
66 | return List.of(
67 | new FieldError(BOOK_OBJECT_NAME, TITLE_FIELD, IT_CANT_BE_NULL_MSG),
68 | new FieldError(BOOK_OBJECT_NAME, ISBN_FIELD, IT_CANT_BE_EMPTY_MSG),
69 | new FieldError(BOOK_OBJECT_NAME, RELEASE_DATE_FIELD, IT_CANT_BE_NULL_MSG));
70 | }
71 |
72 | @Test
73 | public void testEquals() {
74 | ErrorInfo errorInfo1 = createErrorInfo(URL_TEST1).addError(FIELD_TEST1, MESSAGE_TEST1);
75 | ErrorInfo errorInfo2 = createErrorInfo(URL_TEST1).addError(FIELD_TEST1, MESSAGE_TEST1);
76 |
77 | assertThat(errorInfo1.equals(errorInfo2) && errorInfo2.equals(errorInfo1)).isTrue();
78 | }
79 |
80 | @Test
81 | public void testNotEqualsWithDifferentsFields() {
82 | ErrorInfo errorInfo1 = createErrorInfo(URL_TEST1).addError(FIELD_TEST1, MESSAGE_TEST1);
83 | ErrorInfo errorInfo2 = createErrorInfo(URL_TEST1).addError(FIELD_TEST2, MESSAGE_TEST2);
84 |
85 | assertThat(errorInfo1.equals(errorInfo2) || errorInfo2.equals(errorInfo1)).isFalse();
86 | }
87 |
88 | @Test
89 | public void testNotEqualsWithDifferentsObjects() {
90 | ErrorInfo errorInfo = createErrorInfo(URL_TEST1);
91 |
92 | assertThat(errorInfo).isNotEqualTo(new Object());
93 | }
94 |
95 | @Test
96 | public void testHashCode() {
97 | ErrorInfo errorInfo1 = createErrorInfo(URL_TEST1).addError(FIELD_TEST1, MESSAGE_TEST1);
98 | ErrorInfo errorInfo2 = createErrorInfo(URL_TEST1).addError(FIELD_TEST1, MESSAGE_TEST1);
99 |
100 | assertThat(errorInfo1.hashCode()).isEqualTo(errorInfo2.hashCode());
101 | }
102 |
103 | @Test
104 | public void testHasCodeWithDifferentFields() {
105 | ErrorInfo errorInfo1 = createErrorInfo(URL_TEST1).addError(FIELD_TEST1, MESSAGE_TEST1);
106 | ErrorInfo errorInfo2 = createErrorInfo(URL_TEST1).addError(FIELD_TEST2, MESSAGE_TEST2);
107 |
108 | assertThat(errorInfo1.hashCode()).isNotEqualTo(errorInfo2.hashCode());
109 | }
110 |
111 | @Test
112 | public void testToString() {
113 | ErrorInfo errorInfo = createErrorInfo(null);
114 |
115 | assertThatErrorInfoStringContainsAllFields(errorInfo.toString());
116 | }
117 |
118 | private void assertThatErrorInfoStringContainsAllFields(String errorInfoString) {
119 | assertThat(errorInfoString).contains("url=null");
120 | assertThat(errorInfoString).contains("errors=[]");
121 | }
122 |
123 | private ErrorInfo createErrorInfo(String url) {
124 | return new ErrorInfo().setUrl(url);
125 | }
126 | }
--------------------------------------------------------------------------------
/src/test/java/com/edwise/completespring/exceptions/helpers/ErrorItemTest.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.exceptions.helpers;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import static org.assertj.core.api.Assertions.assertThat;
6 |
7 | public class ErrorItemTest {
8 | private static final String FIELD_TEST1 = "authorName";
9 | private static final String MESSAGE_TEXT1 = "El campo no puede ser nulo";
10 | private static final String MESSAGE_TEXT2 = "El campo no puede venir vacio";
11 |
12 | @Test
13 | public void testEquals() {
14 | ErrorItem errorItem1 = createErrorItem(FIELD_TEST1, MESSAGE_TEXT1);
15 | ErrorItem errorItem2 = createErrorItem(FIELD_TEST1, MESSAGE_TEXT1);
16 |
17 | assertThat(errorItem1.equals(errorItem2) && errorItem2.equals(errorItem1)).isTrue();
18 | }
19 |
20 | @Test
21 | public void testNotEqualsWithDifferentFields() {
22 | ErrorItem errorItem1 = createErrorItem(FIELD_TEST1, MESSAGE_TEXT1);
23 | ErrorItem errorItem2 = createErrorItem(FIELD_TEST1, MESSAGE_TEXT2);
24 |
25 | assertThat(errorItem1.equals(errorItem2) || errorItem2.equals(errorItem1)).isFalse();
26 | }
27 |
28 | @Test
29 | public void testNotEqualsWithDifferentObjects() {
30 | ErrorItem errorItem = createErrorItem(FIELD_TEST1, null);
31 |
32 | assertThat(errorItem).isNotEqualTo(new Object());
33 | }
34 |
35 | @Test
36 | public void testHashCode() {
37 | ErrorItem errorItem1 = createErrorItem(FIELD_TEST1, MESSAGE_TEXT1);
38 | ErrorItem errorItem2 = createErrorItem(FIELD_TEST1, MESSAGE_TEXT1);
39 |
40 | assertThat(errorItem1.hashCode()).isEqualTo(errorItem2.hashCode());
41 | }
42 |
43 | @Test
44 | public void testHasCodeWithDifferentFields() {
45 | ErrorItem errorItem1 = createErrorItem(FIELD_TEST1, MESSAGE_TEXT1);
46 | ErrorItem errorItem2 = createErrorItem(FIELD_TEST1, MESSAGE_TEXT2);
47 |
48 | assertThat(errorItem1.hashCode()).isNotEqualTo(errorItem2.hashCode());
49 | }
50 |
51 | @Test
52 | public void testToString() {
53 | ErrorItem errorItem = createErrorItem(null, null);
54 |
55 | assertThatErrorItemStringContainsAllFields(errorItem.toString());
56 | }
57 |
58 | private void assertThatErrorItemStringContainsAllFields(String errorItemString) {
59 | assertThat(errorItemString).contains("field=null");
60 | assertThat(errorItemString).contains("message=null");
61 | }
62 |
63 | private ErrorItem createErrorItem(String field, String message) {
64 | return new ErrorItem()
65 | .setField(field)
66 | .setMessage(message);
67 | }
68 |
69 | }
--------------------------------------------------------------------------------
/src/test/java/com/edwise/completespring/repositories/impl/SequenceIdRepositoryImplTest.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.repositories.impl;
2 |
3 | import com.edwise.completespring.entities.SequenceId;
4 | import com.edwise.completespring.exceptions.SequenceException;
5 | import com.edwise.completespring.services.impl.BookServiceImpl;
6 | import org.junit.jupiter.api.Test;
7 | import org.junit.jupiter.api.extension.ExtendWith;
8 | import org.mockito.InjectMocks;
9 | import org.mockito.Mock;
10 | import org.mockito.junit.jupiter.MockitoExtension;
11 | import org.springframework.data.mongodb.core.FindAndModifyOptions;
12 | import org.springframework.data.mongodb.core.MongoOperations;
13 | import org.springframework.data.mongodb.core.query.Query;
14 | import org.springframework.data.mongodb.core.query.Update;
15 |
16 | import static org.assertj.core.api.Assertions.assertThat;
17 | import static org.assertj.core.api.Assertions.catchThrowable;
18 | import static org.mockito.Mockito.*;
19 |
20 | @ExtendWith(MockitoExtension.class)
21 | public class SequenceIdRepositoryImplTest {
22 |
23 | private static final long TEST_SEQUENCE_ID = 7L;
24 | private static final int ONE_TIME = 1;
25 |
26 | @Mock
27 | private MongoOperations mongoOperation;
28 |
29 | @InjectMocks
30 | private SequenceIdRepositoryImpl repository = new SequenceIdRepositoryImpl();
31 |
32 | @Test
33 | public void testGetNextSequenceId() {
34 | when(mongoOperation.findAndModify(any(Query.class), any(Update.class), any(FindAndModifyOptions.class),
35 | eq(SequenceId.class))).thenReturn(new SequenceId().setId(BookServiceImpl.BOOK_COLLECTION).setSeq(6L));
36 |
37 | long seqId = repository.getNextSequenceId(BookServiceImpl.BOOK_COLLECTION);
38 |
39 | assertThat(seqId).as("Should return a valid sequence").isGreaterThan(0);
40 | verify(mongoOperation, times(ONE_TIME)).findAndModify(any(Query.class), any(Update.class),
41 | any(FindAndModifyOptions.class), eq(SequenceId.class));
42 | }
43 |
44 | @Test
45 | public void testGetNextSequenceIdWhenNotExistsSequence() {
46 | when(mongoOperation.findAndModify(any(Query.class), any(Update.class), any(FindAndModifyOptions.class),
47 | eq(SequenceId.class))).thenReturn(null);
48 |
49 | Throwable thrown = catchThrowable(() -> repository.getNextSequenceId(BookServiceImpl.BOOK_COLLECTION));
50 |
51 | assertThat(thrown)
52 | .isInstanceOf(SequenceException.class)
53 | .hasMessageContaining("Unable to get sequence id for key");
54 | }
55 |
56 | @Test
57 | public void testSave() {
58 | repository.save(new SequenceId().setId(BookServiceImpl.BOOK_COLLECTION).setSeq(TEST_SEQUENCE_ID));
59 |
60 | verify(mongoOperation, times(ONE_TIME)).save(any(SequenceId.class));
61 | }
62 | }
--------------------------------------------------------------------------------
/src/test/java/com/edwise/completespring/services/BookServiceTest.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.services;
2 |
3 | import com.edwise.completespring.entities.AuthorTest;
4 | import com.edwise.completespring.entities.Book;
5 | import com.edwise.completespring.entities.PublisherTest;
6 | import com.edwise.completespring.exceptions.NotFoundException;
7 | import com.edwise.completespring.repositories.BookRepository;
8 | import com.edwise.completespring.repositories.SequenceIdRepository;
9 | import com.edwise.completespring.services.impl.BookServiceImpl;
10 | import com.edwise.completespring.testutil.BookBuilder;
11 | import org.junit.jupiter.api.Test;
12 | import org.junit.jupiter.api.extension.ExtendWith;
13 | import org.mockito.InjectMocks;
14 | import org.mockito.Mock;
15 | import org.mockito.junit.MockitoJUnitRunner;
16 | import org.mockito.junit.jupiter.MockitoExtension;
17 |
18 | import java.time.LocalDate;
19 | import java.util.List;
20 | import java.util.Optional;
21 |
22 | import static org.assertj.core.api.Assertions.assertThat;
23 | import static org.assertj.core.api.Assertions.catchThrowable;
24 | import static org.mockito.Mockito.*;
25 |
26 | @ExtendWith(MockitoExtension.class)
27 | public class BookServiceTest {
28 | private static final long BOOK_ID_TEST1 = 3L;
29 | private static final long BOOK_ID_TEST2 = 400L;
30 | private static final long BOOK_ID_TEST3 = 401L;
31 | private static final String BOOK_TITLE_TEST1 = "Lord of the Rings";
32 | private static final String BOOK_TITLE_TEST2 = "La espada del destino";
33 | private static final String BOOK_ISBN_TEST1 = "11-333-12";
34 | private static final String BOOK_ISBN_TEST2 = "12-1234-12";
35 | private static final String PUBLISHER_NAME_TEST1 = "Editorial Alfaguara";
36 | private static final String PUBLISHER_NAME_TEST2 = "Gigamesh";
37 | private static final String PUBLISHER_COUNTRY_TEST1 = "ES";
38 | private static final String PUBLISHER_COUNTRY_TEST2 = "US";
39 | private static final String AUTHOR_NAME_TEST1 = "Stephen";
40 | private static final String AUTHOR_NAME_TEST2 = "William";
41 | private static final String AUTHOR_SURNAME_TEST1 = "King";
42 | private static final String AUTHOR_SURNAME_TEST2 = "Shakespeare";
43 | private static final LocalDate BOOK_RELEASEDATE_TEST1 = LocalDate.of(2013, 1, 26);
44 | private static final LocalDate BOOK_RELEASEDATE_TEST2 = LocalDate.of(2011, 11, 12);
45 | private static final int ONE_TIME = 1;
46 | private static final int TWO_ITEMS = 2;
47 |
48 | @Mock
49 | BookRepository bookRepository;
50 |
51 | @Mock
52 | SequenceIdRepository sequenceIdRepository;
53 |
54 | @InjectMocks
55 | private BookService service = new BookServiceImpl();
56 |
57 | @Test
58 | public void testFindAll() {
59 | List books = createTestBookList();
60 | when(bookRepository.findAll()).thenReturn(books);
61 |
62 | List result = service.findAll();
63 |
64 | assertThat(result).as("2 elements in page").hasSize(TWO_ITEMS);
65 | verify(bookRepository, times(ONE_TIME)).findAll();
66 | }
67 |
68 | @Test
69 | public void testDelete() {
70 | doNothing().when(bookRepository).deleteById(BOOK_ID_TEST1);
71 |
72 | service.delete(BOOK_ID_TEST1);
73 |
74 | verify(bookRepository, times(ONE_TIME)).deleteById(BOOK_ID_TEST1);
75 | }
76 |
77 | @Test
78 | public void testFindOne() {
79 | when(bookRepository.findById(BOOK_ID_TEST1)).thenReturn(Optional.of(new Book().setId(BOOK_ID_TEST1)));
80 |
81 | Book result = service.findOne(BOOK_ID_TEST1);
82 |
83 | assertThat(result.getId()).isEqualTo(BOOK_ID_TEST1);
84 | verify(bookRepository, timeout(ONE_TIME)).findById(BOOK_ID_TEST1);
85 | }
86 |
87 | @Test
88 | public void testNotFound() {
89 | when(bookRepository.findById(BOOK_ID_TEST1)).thenReturn(Optional.empty());
90 |
91 | Throwable thrown = catchThrowable(() -> service.findOne(BOOK_ID_TEST1));
92 |
93 | assertThat(thrown)
94 | .isInstanceOf(NotFoundException.class)
95 | .hasMessageContaining("Book not found");
96 | }
97 |
98 | @Test
99 | public void testSave() {
100 | Book foo = new BookBuilder().title(BOOK_TITLE_TEST2).build();
101 | Book emptyFoo = new BookBuilder().build();
102 | Book dbBook = emptyFoo.copyFrom(foo).setId(BOOK_ID_TEST1);
103 | when(bookRepository.save(foo)).thenReturn(dbBook);
104 |
105 | Book saved = service.save(foo);
106 |
107 | assertThat(saved).isEqualTo(dbBook);
108 | }
109 |
110 | @Test
111 | public void testCreate() {
112 | Book foo = new BookBuilder().title(BOOK_TITLE_TEST2).build();
113 | Book emptyFoo = new BookBuilder().build();
114 | Book dbBook = emptyFoo.copyFrom(foo).setId(BOOK_ID_TEST1);
115 | when(sequenceIdRepository.getNextSequenceId(BookServiceImpl.BOOK_COLLECTION)).thenReturn(BOOK_ID_TEST3);
116 | when(bookRepository.save(foo)).thenReturn(dbBook.setId(BOOK_ID_TEST3));
117 |
118 | Book saved = service.create(foo);
119 |
120 | assertThat(saved).isEqualTo(dbBook);
121 | assertThat(saved.getId()).isEqualTo(BOOK_ID_TEST3);
122 | }
123 |
124 | @Test
125 | public void testFindByTitle() {
126 | List bookResults =
127 | List.of(new BookBuilder().id(BOOK_ID_TEST1).title(BOOK_TITLE_TEST1).build());
128 | when(bookRepository.findByTitle(BOOK_TITLE_TEST1)).thenReturn(bookResults);
129 |
130 | List result = service.findByTitle(BOOK_TITLE_TEST1);
131 |
132 | assertThat(result).hasSize(ONE_TIME);
133 | verify(bookRepository, timeout(ONE_TIME)).findByTitle(BOOK_TITLE_TEST1);
134 | }
135 |
136 | @Test
137 | public void testFindByReleaseDate() {
138 | List bookResults =
139 | List.of(new BookBuilder().id(BOOK_ID_TEST1).releaseDate(BOOK_RELEASEDATE_TEST1).build());
140 | when(bookRepository.findByReleaseDate(BOOK_RELEASEDATE_TEST1)).thenReturn(bookResults);
141 |
142 | List result = service.findByReleaseDate(BOOK_RELEASEDATE_TEST1);
143 |
144 | assertThat(result).hasSize(ONE_TIME);
145 | verify(bookRepository, timeout(ONE_TIME)).findByReleaseDate(BOOK_RELEASEDATE_TEST1);
146 | }
147 |
148 | private List createTestBookList() {
149 | Book book1 = new BookBuilder()
150 | .id(BOOK_ID_TEST1)
151 | .title(BOOK_TITLE_TEST1)
152 | .authors(List.of(AuthorTest.createAuthor(AUTHOR_NAME_TEST1, AUTHOR_SURNAME_TEST1)))
153 | .isbn(BOOK_ISBN_TEST1)
154 | .releaseDate(BOOK_RELEASEDATE_TEST1)
155 | .publisher(PublisherTest.createPublisher(PUBLISHER_NAME_TEST1, PUBLISHER_COUNTRY_TEST1, false))
156 | .build();
157 | Book book2 = new BookBuilder()
158 | .id(BOOK_ID_TEST2)
159 | .title(BOOK_TITLE_TEST2)
160 | .authors(List.of(AuthorTest.createAuthor(AUTHOR_NAME_TEST2, AUTHOR_SURNAME_TEST2)))
161 | .isbn(BOOK_ISBN_TEST2)
162 | .releaseDate(BOOK_RELEASEDATE_TEST2)
163 | .publisher(PublisherTest.createPublisher(PUBLISHER_NAME_TEST2, PUBLISHER_COUNTRY_TEST2, true))
164 | .build();
165 |
166 | return List.of(book1, book2);
167 | }
168 |
169 | }
170 |
--------------------------------------------------------------------------------
/src/test/java/com/edwise/completespring/testutil/BookBuilder.java:
--------------------------------------------------------------------------------
1 | package com.edwise.completespring.testutil;
2 |
3 | import com.edwise.completespring.entities.Author;
4 | import com.edwise.completespring.entities.Book;
5 | import com.edwise.completespring.entities.Publisher;
6 |
7 | import java.time.LocalDate;
8 | import java.util.List;
9 |
10 | public class BookBuilder {
11 |
12 | private final Book book;
13 |
14 | public BookBuilder() {
15 | book = new Book();
16 | }
17 |
18 | public BookBuilder id(Long id) {
19 | book.setId(id);
20 | return this;
21 | }
22 |
23 | public BookBuilder title(String title) {
24 | book.setTitle(title);
25 | return this;
26 | }
27 |
28 | public BookBuilder authors(List authors) {
29 | book.setAuthors(authors);
30 | return this;
31 | }
32 |
33 | public BookBuilder isbn(String isbn) {
34 | book.setIsbn(isbn);
35 | return this;
36 | }
37 |
38 | public BookBuilder releaseDate(LocalDate releaseDate) {
39 | book.setReleaseDate(releaseDate);
40 | return this;
41 | }
42 |
43 | public BookBuilder publisher(Publisher publisher) {
44 | book.setPublisher(publisher);
45 | return this;
46 | }
47 |
48 | public Book build() {
49 | return book;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/test/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------