├── .gitignore
├── .mvn
└── wrapper
│ ├── MavenWrapperDownloader.java
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── .travis.yml
├── README.md
├── appfile.log
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
├── main
├── java
│ └── com
│ │ └── cursodsousa
│ │ └── libraryapi
│ │ ├── LibraryApiApplication.java
│ │ ├── api
│ │ ├── ApplicationControllerAdvice.java
│ │ ├── dto
│ │ │ ├── BookDTO.java
│ │ │ ├── LoanDTO.java
│ │ │ ├── LoanFilterDTO.java
│ │ │ └── ReturnedLoanDTO.java
│ │ ├── exception
│ │ │ └── ApiErros.java
│ │ └── resource
│ │ │ ├── BookController.java
│ │ │ └── LoanController.java
│ │ ├── config
│ │ └── SwaggerConfig.java
│ │ ├── exception
│ │ └── BusinessException.java
│ │ ├── model
│ │ ├── entity
│ │ │ ├── Book.java
│ │ │ └── Loan.java
│ │ └── repository
│ │ │ ├── BookRepository.java
│ │ │ └── LoanRepository.java
│ │ └── service
│ │ ├── BookService.java
│ │ ├── EmailService.java
│ │ ├── LoanService.java
│ │ ├── ScheduleService.java
│ │ └── impl
│ │ ├── BookServiceImpl.java
│ │ ├── EmailServiceImpl.java
│ │ └── LoanServiceImpl.java
└── resources
│ └── application.properties
└── test
└── java
└── com
└── cursodsousa
└── libraryapi
├── LibraryApiApplicationTests.java
├── api
└── resource
│ ├── BookControllerTest.java
│ └── LoanControllerTest.java
├── model
└── repository
│ ├── BookRepositoryTest.java
│ └── LoanRepositoryTest.java
└── service
├── BookServiceTest.java
└── LoanServiceTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | target/
3 | !.mvn/wrapper/maven-wrapper.jar
4 | !**/src/main/**
5 | !**/src/test/**
6 |
7 | ### STS ###
8 | .apt_generated
9 | .classpath
10 | .factorypath
11 | .project
12 | .settings
13 | .springBeans
14 | .sts4-cache
15 |
16 | ### IntelliJ IDEA ###
17 | .idea
18 | *.iws
19 | *.iml
20 | *.ipr
21 |
22 | ### NetBeans ###
23 | /nbproject/private/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 | build/
29 |
30 | ### VS Code ###
31 | .vscode/
32 |
33 | appfile.log
34 |
--------------------------------------------------------------------------------
/.mvn/wrapper/MavenWrapperDownloader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2019 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | import java.net.*;
17 | import java.io.*;
18 | import java.nio.channels.*;
19 | import java.util.Properties;
20 |
21 | public class MavenWrapperDownloader {
22 |
23 | private static final String WRAPPER_VERSION = "0.5.5";
24 | /**
25 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
26 | */
27 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
28 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
29 |
30 | /**
31 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
32 | * use instead of the default one.
33 | */
34 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
35 | ".mvn/wrapper/maven-wrapper.properties";
36 |
37 | /**
38 | * Path where the maven-wrapper.jar will be saved to.
39 | */
40 | private static final String MAVEN_WRAPPER_JAR_PATH =
41 | ".mvn/wrapper/maven-wrapper.jar";
42 |
43 | /**
44 | * Name of the property which should be used to override the default download url for the wrapper.
45 | */
46 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
47 |
48 | public static void main(String args[]) {
49 | System.out.println("- Downloader started");
50 | File baseDirectory = new File(args[0]);
51 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
52 |
53 | // If the maven-wrapper.properties exists, read it and check if it contains a custom
54 | // wrapperUrl parameter.
55 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
56 | String url = DEFAULT_DOWNLOAD_URL;
57 | if(mavenWrapperPropertyFile.exists()) {
58 | FileInputStream mavenWrapperPropertyFileInputStream = null;
59 | try {
60 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
61 | Properties mavenWrapperProperties = new Properties();
62 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
63 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
64 | } catch (IOException e) {
65 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
66 | } finally {
67 | try {
68 | if(mavenWrapperPropertyFileInputStream != null) {
69 | mavenWrapperPropertyFileInputStream.close();
70 | }
71 | } catch (IOException e) {
72 | // Ignore ...
73 | }
74 | }
75 | }
76 | System.out.println("- Downloading from: " + url);
77 |
78 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
79 | if(!outputFile.getParentFile().exists()) {
80 | if(!outputFile.getParentFile().mkdirs()) {
81 | System.out.println(
82 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
83 | }
84 | }
85 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
86 | try {
87 | downloadFileFromURL(url, outputFile);
88 | System.out.println("Done");
89 | System.exit(0);
90 | } catch (Throwable e) {
91 | System.out.println("- Error downloading");
92 | e.printStackTrace();
93 | System.exit(1);
94 | }
95 | }
96 |
97 | private static void downloadFileFromURL(String urlString, File destination) throws Exception {
98 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
99 | String username = System.getenv("MVNW_USERNAME");
100 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
101 | Authenticator.setDefault(new Authenticator() {
102 | @Override
103 | protected PasswordAuthentication getPasswordAuthentication() {
104 | return new PasswordAuthentication(username, password);
105 | }
106 | });
107 | }
108 | URL website = new URL(urlString);
109 | ReadableByteChannel rbc;
110 | rbc = Channels.newChannel(website.openStream());
111 | FileOutputStream fos = new FileOutputStream(destination);
112 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
113 | fos.close();
114 | rbc.close();
115 | }
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dougllasfps/library-api/6c4307910feeefdb4455f4df0ca4ddbbd3a9060b/.mvn/wrapper/maven-wrapper.jar
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.2/apache-maven-3.6.2-bin.zip
2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk:
3 | - oraclejdk8
4 | dist: trusty
5 | before_install:
6 | - chmod +x mvnw
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # library-api
2 |
3 | [](https://travis-ci.com/dougllasfps/library-api)
4 |
5 | [](https://codecov.io/gh/dougllasfps/library-api)
6 |
7 |
--------------------------------------------------------------------------------
/mvnw:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # ----------------------------------------------------------------------------
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # https://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License.
19 | # ----------------------------------------------------------------------------
20 |
21 | # ----------------------------------------------------------------------------
22 | # Maven2 Start Up Batch script
23 | #
24 | # Required ENV vars:
25 | # ------------------
26 | # JAVA_HOME - location of a JDK home dir
27 | #
28 | # Optional ENV vars
29 | # -----------------
30 | # M2_HOME - location of maven2's installed home dir
31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven
32 | # e.g. to debug Maven itself, use
33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files
35 | # ----------------------------------------------------------------------------
36 |
37 | if [ -z "$MAVEN_SKIP_RC" ] ; then
38 |
39 | if [ -f /etc/mavenrc ] ; then
40 | . /etc/mavenrc
41 | fi
42 |
43 | if [ -f "$HOME/.mavenrc" ] ; then
44 | . "$HOME/.mavenrc"
45 | fi
46 |
47 | fi
48 |
49 | # OS specific support. $var _must_ be set to either true or false.
50 | cygwin=false;
51 | darwin=false;
52 | mingw=false
53 | case "`uname`" in
54 | CYGWIN*) cygwin=true ;;
55 | MINGW*) mingw=true;;
56 | Darwin*) darwin=true
57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
59 | if [ -z "$JAVA_HOME" ]; then
60 | if [ -x "/usr/libexec/java_home" ]; then
61 | export JAVA_HOME="`/usr/libexec/java_home`"
62 | else
63 | export JAVA_HOME="/Library/Java/Home"
64 | fi
65 | fi
66 | ;;
67 | esac
68 |
69 | if [ -z "$JAVA_HOME" ] ; then
70 | if [ -r /etc/gentoo-release ] ; then
71 | JAVA_HOME=`java-config --jre-home`
72 | fi
73 | fi
74 |
75 | if [ -z "$M2_HOME" ] ; then
76 | ## resolve links - $0 may be a link to maven's home
77 | PRG="$0"
78 |
79 | # need this for relative symlinks
80 | while [ -h "$PRG" ] ; do
81 | ls=`ls -ld "$PRG"`
82 | link=`expr "$ls" : '.*-> \(.*\)$'`
83 | if expr "$link" : '/.*' > /dev/null; then
84 | PRG="$link"
85 | else
86 | PRG="`dirname "$PRG"`/$link"
87 | fi
88 | done
89 |
90 | saveddir=`pwd`
91 |
92 | M2_HOME=`dirname "$PRG"`/..
93 |
94 | # make it fully qualified
95 | M2_HOME=`cd "$M2_HOME" && pwd`
96 |
97 | cd "$saveddir"
98 | # echo Using m2 at $M2_HOME
99 | fi
100 |
101 | # For Cygwin, ensure paths are in UNIX format before anything is touched
102 | if $cygwin ; then
103 | [ -n "$M2_HOME" ] &&
104 | M2_HOME=`cygpath --unix "$M2_HOME"`
105 | [ -n "$JAVA_HOME" ] &&
106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
107 | [ -n "$CLASSPATH" ] &&
108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
109 | fi
110 |
111 | # For Mingw, ensure paths are in UNIX format before anything is touched
112 | if $mingw ; then
113 | [ -n "$M2_HOME" ] &&
114 | M2_HOME="`(cd "$M2_HOME"; pwd)`"
115 | [ -n "$JAVA_HOME" ] &&
116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
117 | fi
118 |
119 | if [ -z "$JAVA_HOME" ]; then
120 | javaExecutable="`which javac`"
121 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
122 | # readlink(1) is not available as standard on Solaris 10.
123 | readLink=`which readlink`
124 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
125 | if $darwin ; then
126 | javaHome="`dirname \"$javaExecutable\"`"
127 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
128 | else
129 | javaExecutable="`readlink -f \"$javaExecutable\"`"
130 | fi
131 | javaHome="`dirname \"$javaExecutable\"`"
132 | javaHome=`expr "$javaHome" : '\(.*\)/bin'`
133 | JAVA_HOME="$javaHome"
134 | export JAVA_HOME
135 | fi
136 | fi
137 | fi
138 |
139 | if [ -z "$JAVACMD" ] ; then
140 | if [ -n "$JAVA_HOME" ] ; then
141 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
142 | # IBM's JDK on AIX uses strange locations for the executables
143 | JAVACMD="$JAVA_HOME/jre/sh/java"
144 | else
145 | JAVACMD="$JAVA_HOME/bin/java"
146 | fi
147 | else
148 | JAVACMD="`which java`"
149 | fi
150 | fi
151 |
152 | if [ ! -x "$JAVACMD" ] ; then
153 | echo "Error: JAVA_HOME is not defined correctly." >&2
154 | echo " We cannot execute $JAVACMD" >&2
155 | exit 1
156 | fi
157 |
158 | if [ -z "$JAVA_HOME" ] ; then
159 | echo "Warning: JAVA_HOME environment variable is not set."
160 | fi
161 |
162 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
163 |
164 | # traverses directory structure from process work directory to filesystem root
165 | # first directory with .mvn subdirectory is considered project base directory
166 | find_maven_basedir() {
167 |
168 | if [ -z "$1" ]
169 | then
170 | echo "Path not specified to find_maven_basedir"
171 | return 1
172 | fi
173 |
174 | basedir="$1"
175 | wdir="$1"
176 | while [ "$wdir" != '/' ] ; do
177 | if [ -d "$wdir"/.mvn ] ; then
178 | basedir=$wdir
179 | break
180 | fi
181 | # workaround for JBEAP-8937 (on Solaris 10/Sparc)
182 | if [ -d "${wdir}" ]; then
183 | wdir=`cd "$wdir/.."; pwd`
184 | fi
185 | # end of workaround
186 | done
187 | echo "${basedir}"
188 | }
189 |
190 | # concatenates all lines of a file
191 | concat_lines() {
192 | if [ -f "$1" ]; then
193 | echo "$(tr -s '\n' ' ' < "$1")"
194 | fi
195 | }
196 |
197 | BASE_DIR=`find_maven_basedir "$(pwd)"`
198 | if [ -z "$BASE_DIR" ]; then
199 | exit 1;
200 | fi
201 |
202 | ##########################################################################################
203 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
204 | # This allows using the maven wrapper in projects that prohibit checking in binary data.
205 | ##########################################################################################
206 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
207 | if [ "$MVNW_VERBOSE" = true ]; then
208 | echo "Found .mvn/wrapper/maven-wrapper.jar"
209 | fi
210 | else
211 | if [ "$MVNW_VERBOSE" = true ]; then
212 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
213 | fi
214 | if [ -n "$MVNW_REPOURL" ]; then
215 | jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
216 | else
217 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
218 | fi
219 | while IFS="=" read key value; do
220 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
221 | esac
222 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
223 | if [ "$MVNW_VERBOSE" = true ]; then
224 | echo "Downloading from: $jarUrl"
225 | fi
226 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
227 | if $cygwin; then
228 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
229 | fi
230 |
231 | if command -v wget > /dev/null; then
232 | if [ "$MVNW_VERBOSE" = true ]; then
233 | echo "Found wget ... using wget"
234 | fi
235 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
236 | wget "$jarUrl" -O "$wrapperJarPath"
237 | else
238 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
239 | fi
240 | elif command -v curl > /dev/null; then
241 | if [ "$MVNW_VERBOSE" = true ]; then
242 | echo "Found curl ... using curl"
243 | fi
244 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
245 | curl -o "$wrapperJarPath" "$jarUrl" -f
246 | else
247 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
248 | fi
249 |
250 | else
251 | if [ "$MVNW_VERBOSE" = true ]; then
252 | echo "Falling back to using Java to download"
253 | fi
254 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
255 | # For Cygwin, switch paths to Windows format before running javac
256 | if $cygwin; then
257 | javaClass=`cygpath --path --windows "$javaClass"`
258 | fi
259 | if [ -e "$javaClass" ]; then
260 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
261 | if [ "$MVNW_VERBOSE" = true ]; then
262 | echo " - Compiling MavenWrapperDownloader.java ..."
263 | fi
264 | # Compiling the Java class
265 | ("$JAVA_HOME/bin/javac" "$javaClass")
266 | fi
267 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
268 | # Running the downloader
269 | if [ "$MVNW_VERBOSE" = true ]; then
270 | echo " - Running MavenWrapperDownloader.java ..."
271 | fi
272 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
273 | fi
274 | fi
275 | fi
276 | fi
277 | ##########################################################################################
278 | # End of extension
279 | ##########################################################################################
280 |
281 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
282 | if [ "$MVNW_VERBOSE" = true ]; then
283 | echo $MAVEN_PROJECTBASEDIR
284 | fi
285 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
286 |
287 | # For Cygwin, switch paths to Windows format before running java
288 | if $cygwin; then
289 | [ -n "$M2_HOME" ] &&
290 | M2_HOME=`cygpath --path --windows "$M2_HOME"`
291 | [ -n "$JAVA_HOME" ] &&
292 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
293 | [ -n "$CLASSPATH" ] &&
294 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
295 | [ -n "$MAVEN_PROJECTBASEDIR" ] &&
296 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
297 | fi
298 |
299 | # Provide a "standardized" way to retrieve the CLI args that will
300 | # work with both Windows and non-Windows executions.
301 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
302 | export MAVEN_CMD_LINE_ARGS
303 |
304 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
305 |
306 | exec "$JAVACMD" \
307 | $MAVEN_OPTS \
308 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
309 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
310 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
311 |
--------------------------------------------------------------------------------
/mvnw.cmd:
--------------------------------------------------------------------------------
1 | @REM ----------------------------------------------------------------------------
2 | @REM Licensed to the Apache Software Foundation (ASF) under one
3 | @REM or more contributor license agreements. See the NOTICE file
4 | @REM distributed with this work for additional information
5 | @REM regarding copyright ownership. The ASF licenses this file
6 | @REM to you under the Apache License, Version 2.0 (the
7 | @REM "License"); you may not use this file except in compliance
8 | @REM with the License. You may obtain a copy of the License at
9 | @REM
10 | @REM https://www.apache.org/licenses/LICENSE-2.0
11 | @REM
12 | @REM Unless required by applicable law or agreed to in writing,
13 | @REM software distributed under the License is distributed on an
14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | @REM KIND, either express or implied. See the License for the
16 | @REM specific language governing permissions and limitations
17 | @REM under the License.
18 | @REM ----------------------------------------------------------------------------
19 |
20 | @REM ----------------------------------------------------------------------------
21 | @REM Maven2 Start Up Batch script
22 | @REM
23 | @REM Required ENV vars:
24 | @REM JAVA_HOME - location of a JDK home dir
25 | @REM
26 | @REM Optional ENV vars
27 | @REM M2_HOME - location of maven2's installed home dir
28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
31 | @REM e.g. to debug Maven itself, use
32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
34 | @REM ----------------------------------------------------------------------------
35 |
36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
37 | @echo off
38 | @REM set title of command window
39 | title %0
40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
42 |
43 | @REM set %HOME% to equivalent of $HOME
44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
45 |
46 | @REM Execute a user defined script before this one
47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending
49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
51 | :skipRcPre
52 |
53 | @setlocal
54 |
55 | set ERROR_CODE=0
56 |
57 | @REM To isolate internal variables from possible post scripts, we use another setlocal
58 | @setlocal
59 |
60 | @REM ==== START VALIDATION ====
61 | if not "%JAVA_HOME%" == "" goto OkJHome
62 |
63 | echo.
64 | echo Error: JAVA_HOME not found in your environment. >&2
65 | echo Please set the JAVA_HOME variable in your environment to match the >&2
66 | echo location of your Java installation. >&2
67 | echo.
68 | goto error
69 |
70 | :OkJHome
71 | if exist "%JAVA_HOME%\bin\java.exe" goto init
72 |
73 | echo.
74 | echo Error: JAVA_HOME is set to an invalid directory. >&2
75 | echo JAVA_HOME = "%JAVA_HOME%" >&2
76 | echo Please set the JAVA_HOME variable in your environment to match the >&2
77 | echo location of your Java installation. >&2
78 | echo.
79 | goto error
80 |
81 | @REM ==== END VALIDATION ====
82 |
83 | :init
84 |
85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
86 | @REM Fallback to current working directory if not found.
87 |
88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
90 |
91 | set EXEC_DIR=%CD%
92 | set WDIR=%EXEC_DIR%
93 | :findBaseDir
94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound
95 | cd ..
96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound
97 | set WDIR=%CD%
98 | goto findBaseDir
99 |
100 | :baseDirFound
101 | set MAVEN_PROJECTBASEDIR=%WDIR%
102 | cd "%EXEC_DIR%"
103 | goto endDetectBaseDir
104 |
105 | :baseDirNotFound
106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
107 | cd "%EXEC_DIR%"
108 |
109 | :endDetectBaseDir
110 |
111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
112 |
113 | @setlocal EnableExtensions EnableDelayedExpansion
114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
116 |
117 | :endReadAdditionalConfig
118 |
119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
122 |
123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
124 |
125 | FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
127 | )
128 |
129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data.
131 | if exist %WRAPPER_JAR% (
132 | if "%MVNW_VERBOSE%" == "true" (
133 | echo Found %WRAPPER_JAR%
134 | )
135 | ) else (
136 | if not "%MVNW_REPOURL%" == "" (
137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
138 | )
139 | if "%MVNW_VERBOSE%" == "true" (
140 | echo Couldn't find %WRAPPER_JAR%, downloading it ...
141 | echo Downloading from: %DOWNLOAD_URL%
142 | )
143 |
144 | powershell -Command "&{"^
145 | "$webclient = new-object System.Net.WebClient;"^
146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
148 | "}"^
149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
150 | "}"
151 | if "%MVNW_VERBOSE%" == "true" (
152 | echo Finished downloading %WRAPPER_JAR%
153 | )
154 | )
155 | @REM End of extension
156 |
157 | @REM Provide a "standardized" way to retrieve the CLI args that will
158 | @REM work with both Windows and non-Windows executions.
159 | set MAVEN_CMD_LINE_ARGS=%*
160 |
161 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
162 | if ERRORLEVEL 1 goto error
163 | goto end
164 |
165 | :error
166 | set ERROR_CODE=1
167 |
168 | :end
169 | @endlocal & set ERROR_CODE=%ERROR_CODE%
170 |
171 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
172 | @REM check for post script, once with legacy .bat ending and once with .cmd ending
173 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
174 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
175 | :skipRcPost
176 |
177 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
178 | if "%MAVEN_BATCH_PAUSE%" == "on" pause
179 |
180 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
181 |
182 | exit /B %ERROR_CODE%
183 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 2.2.1.RELEASE
9 |
10 |
11 |
12 | com.cursodsousa
13 | library-api
14 | 0.0.1-SNAPSHOT
15 | library-api
16 | Projeto de controle de aluguel de livros
17 |
18 |
19 | 1.8
20 |
21 |
22 |
23 |
24 | org.springframework.boot
25 | spring-boot-starter-data-jpa
26 |
27 |
28 |
29 | org.springframework.boot
30 | spring-boot-starter-web
31 |
32 |
33 |
34 | org.springframework.boot
35 | spring-boot-starter-mail
36 |
37 |
38 |
39 | org.springframework.boot
40 | spring-boot-starter-actuator
41 |
42 |
43 |
44 | de.codecentric
45 | spring-boot-admin-starter-client
46 | 2.2.0
47 |
48 |
49 |
50 | org.springframework.boot
51 | spring-boot-devtools
52 | runtime
53 | true
54 |
55 |
56 |
57 | com.h2database
58 | h2
59 | runtime
60 |
61 |
62 |
63 | org.projectlombok
64 | lombok
65 | true
66 |
67 |
68 |
69 | org.modelmapper
70 | modelmapper
71 | 2.3.0
72 |
73 |
74 |
75 | io.springfox
76 | springfox-swagger2
77 | 2.6.1
78 |
79 |
80 |
81 | io.springfox
82 | springfox-swagger-ui
83 | 2.6.1
84 |
85 |
86 |
87 | org.springframework.boot
88 | spring-boot-starter-test
89 | test
90 |
91 |
92 | org.junit.vintage
93 | junit-vintage-engine
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | org.springframework.boot
103 | spring-boot-maven-plugin
104 |
105 |
106 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/src/main/java/com/cursodsousa/libraryapi/LibraryApiApplication.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi;
2 |
3 | import com.cursodsousa.libraryapi.service.EmailService;
4 | import org.modelmapper.ModelMapper;
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 | import org.springframework.mail.SimpleMailMessage;
12 | import org.springframework.mail.javamail.JavaMailSender;
13 | import org.springframework.scheduling.annotation.EnableScheduling;
14 | import org.springframework.scheduling.annotation.Scheduled;
15 |
16 | import java.util.Arrays;
17 | import java.util.List;
18 |
19 | @SpringBootApplication
20 | @EnableScheduling
21 | public class LibraryApiApplication {
22 |
23 | @Bean
24 | public ModelMapper modelMapper(){
25 | return new ModelMapper();
26 | }
27 |
28 | public static void main(String[] args) {
29 | SpringApplication.run(LibraryApiApplication.class, args);
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/cursodsousa/libraryapi/api/ApplicationControllerAdvice.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.api;
2 |
3 | import com.cursodsousa.libraryapi.api.exception.ApiErros;
4 | import com.cursodsousa.libraryapi.exception.BusinessException;
5 | import org.springframework.http.HttpStatus;
6 | import org.springframework.http.ResponseEntity;
7 | import org.springframework.validation.BindingResult;
8 | import org.springframework.web.bind.MethodArgumentNotValidException;
9 | import org.springframework.web.bind.annotation.ExceptionHandler;
10 | import org.springframework.web.bind.annotation.ResponseStatus;
11 | import org.springframework.web.bind.annotation.RestControllerAdvice;
12 | import org.springframework.web.server.ResponseStatusException;
13 |
14 | @RestControllerAdvice
15 | public class ApplicationControllerAdvice {
16 |
17 | @ExceptionHandler(MethodArgumentNotValidException.class)
18 | @ResponseStatus(HttpStatus.BAD_REQUEST)
19 | public ApiErros handleValidationExceptions(MethodArgumentNotValidException ex){
20 | BindingResult bindingResult = ex.getBindingResult();
21 | return new ApiErros(bindingResult);
22 | }
23 |
24 | @ExceptionHandler(BusinessException.class)
25 | @ResponseStatus(HttpStatus.BAD_REQUEST)
26 | public ApiErros handleBusinessException(BusinessException ex){
27 | return new ApiErros(ex);
28 | }
29 |
30 | @ExceptionHandler(ResponseStatusException.class)
31 | public ResponseEntity handleResponseStatusException( ResponseStatusException ex ){
32 | return new ResponseEntity(new ApiErros(ex), ex.getStatus());
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/cursodsousa/libraryapi/api/dto/BookDTO.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.api.dto;
2 |
3 | import lombok.*;
4 |
5 | import javax.validation.constraints.NotEmpty;
6 |
7 | @Data
8 | @Builder
9 | @NoArgsConstructor
10 | @AllArgsConstructor
11 | public class BookDTO {
12 |
13 | private Long id;
14 |
15 | @NotEmpty
16 | private String title;
17 |
18 | @NotEmpty
19 | private String author;
20 |
21 | @NotEmpty
22 | private String isbn;
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/cursodsousa/libraryapi/api/dto/LoanDTO.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.api.dto;
2 |
3 | import com.fasterxml.jackson.annotation.JsonIgnore;
4 | import lombok.AllArgsConstructor;
5 | import lombok.Builder;
6 | import lombok.Data;
7 | import lombok.NoArgsConstructor;
8 |
9 | import javax.validation.constraints.NotEmpty;
10 |
11 | @Data
12 | @Builder
13 | @NoArgsConstructor
14 | @AllArgsConstructor
15 | public class LoanDTO {
16 |
17 | private Long id;
18 | @NotEmpty
19 | private String isbn;
20 | @NotEmpty
21 | private String customer;
22 | @NotEmpty
23 | private String email;
24 | private BookDTO book;
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/cursodsousa/libraryapi/api/dto/LoanFilterDTO.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.api.dto;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Builder;
5 | import lombok.Data;
6 | import lombok.NoArgsConstructor;
7 |
8 | @Data
9 | @AllArgsConstructor
10 | @NoArgsConstructor
11 | @Builder
12 | public class LoanFilterDTO {
13 |
14 | private String isbn;
15 | private String customer;
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/cursodsousa/libraryapi/api/dto/ReturnedLoanDTO.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.api.dto;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Builder;
5 | import lombok.Data;
6 | import lombok.NoArgsConstructor;
7 |
8 | @Builder
9 | @AllArgsConstructor
10 | @NoArgsConstructor
11 | @Data
12 | public class ReturnedLoanDTO {
13 | private Boolean returned;
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/cursodsousa/libraryapi/api/exception/ApiErros.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.api.exception;
2 |
3 | import com.cursodsousa.libraryapi.exception.BusinessException;
4 | import org.springframework.validation.BindingResult;
5 | import org.springframework.web.server.ResponseStatusException;
6 |
7 | import java.util.ArrayList;
8 | import java.util.Arrays;
9 | import java.util.List;
10 |
11 | public class ApiErros {
12 | private List errors;
13 |
14 | public ApiErros(BindingResult bindingResult) {
15 | this.errors = new ArrayList<>();
16 | bindingResult.getAllErrors().forEach( error -> this.errors.add(error.getDefaultMessage()) );
17 | }
18 |
19 | public ApiErros(BusinessException ex) {
20 | this.errors = Arrays.asList(ex.getMessage());
21 | }
22 |
23 | public ApiErros(ResponseStatusException ex) {
24 | this.errors = Arrays.asList(ex.getReason());
25 | }
26 |
27 | public List getErrors() {
28 | return errors;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/cursodsousa/libraryapi/api/resource/BookController.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.api.resource;
2 |
3 | import com.cursodsousa.libraryapi.api.dto.BookDTO;
4 | import com.cursodsousa.libraryapi.api.dto.LoanDTO;
5 | import com.cursodsousa.libraryapi.api.exception.ApiErros;
6 | import com.cursodsousa.libraryapi.exception.BusinessException;
7 | import com.cursodsousa.libraryapi.model.entity.Book;
8 | import com.cursodsousa.libraryapi.model.entity.Loan;
9 | import com.cursodsousa.libraryapi.service.BookService;
10 | import com.cursodsousa.libraryapi.service.LoanService;
11 | import io.swagger.annotations.Api;
12 | import io.swagger.annotations.ApiOperation;
13 | import io.swagger.annotations.ApiResponse;
14 | import io.swagger.annotations.ApiResponses;
15 | import lombok.RequiredArgsConstructor;
16 | import lombok.extern.slf4j.Slf4j;
17 | import org.modelmapper.ModelMapper;
18 | import org.springframework.beans.factory.annotation.Autowired;
19 | import org.springframework.data.domain.Page;
20 | import org.springframework.data.domain.PageImpl;
21 | import org.springframework.data.domain.PageRequest;
22 | import org.springframework.data.domain.Pageable;
23 | import org.springframework.http.HttpStatus;
24 | import org.springframework.validation.BindingResult;
25 | import org.springframework.web.bind.MethodArgumentNotValidException;
26 | import org.springframework.web.bind.annotation.*;
27 | import org.springframework.web.server.ResponseStatusException;
28 |
29 | import javax.validation.Valid;
30 | import java.util.List;
31 | import java.util.stream.Collectors;
32 |
33 | @RestController
34 | @RequestMapping("/api/books")
35 | @RequiredArgsConstructor
36 | @Api("Book API")
37 | @Slf4j
38 | public class BookController {
39 |
40 | private final BookService service;
41 | private final ModelMapper modelMapper;
42 | private final LoanService loanService;
43 |
44 | @PostMapping
45 | @ResponseStatus(HttpStatus.CREATED)
46 | @ApiOperation("Create a book")
47 | public BookDTO create( @RequestBody @Valid BookDTO dto ){
48 | log.info(" creating a book for isbn: {} ", dto.getIsbn());
49 | Book entity = modelMapper.map( dto, Book.class );
50 | entity = service.save(entity);
51 | return modelMapper.map(entity, BookDTO.class);
52 | }
53 |
54 | @GetMapping("{id}")
55 | @ApiOperation("Get a book details by id")
56 | public BookDTO get( @PathVariable Long id ){
57 | log.info(" obtaining details for book id: {} ", id);
58 | return service
59 | .getById(id)
60 | .map( book -> modelMapper.map(book, BookDTO.class) )
61 | .orElseThrow( () -> new ResponseStatusException(HttpStatus.NOT_FOUND) );
62 | }
63 |
64 | @DeleteMapping("{id}")
65 | @ResponseStatus(HttpStatus.NO_CONTENT)
66 | @ApiOperation("Deletes a book by id")
67 | public void delete(@PathVariable Long id){
68 | log.info(" deleting book of id: {} ", id);
69 | Book book = service.getById(id).orElseThrow( () -> new ResponseStatusException(HttpStatus.NOT_FOUND) );
70 | service.delete(book);
71 | }
72 |
73 | @PutMapping("{id}")
74 | @ApiOperation("Updates a book")
75 | public BookDTO update( @PathVariable Long id, @RequestBody @Valid BookDTO dto){
76 | log.info(" updating book of id: {} ", id);
77 | return service.getById(id).map( book -> {
78 |
79 | book.setAuthor(dto.getAuthor());
80 | book.setTitle(dto.getTitle());
81 | book = service.update(book);
82 | return modelMapper.map(book, BookDTO.class);
83 |
84 | }).orElseThrow( () -> new ResponseStatusException(HttpStatus.NOT_FOUND) );
85 | }
86 |
87 | @GetMapping
88 | @ApiOperation("Lists books by params")
89 | public Page find( BookDTO dto, Pageable pageRequest ){
90 | Book filter = modelMapper.map(dto, Book.class);
91 | Page result = service.find(filter, pageRequest);
92 | List list = result.getContent()
93 | .stream()
94 | .map(entity -> modelMapper.map(entity, BookDTO.class))
95 | .collect(Collectors.toList());
96 |
97 | return new PageImpl( list, pageRequest, result.getTotalElements() );
98 | }
99 |
100 | @GetMapping("{id}/loans")
101 | public Page loansByBook( @PathVariable Long id, Pageable pageable ){
102 | Book book = service.getById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
103 | Page result = loanService.getLoansByBook(book, pageable);
104 | List list = result.getContent()
105 | .stream()
106 | .map(loan -> {
107 | Book loanBook = loan.getBook();
108 | BookDTO bookDTO = modelMapper.map(loanBook, BookDTO.class);
109 | LoanDTO loanDTO = modelMapper.map(loan, LoanDTO.class);
110 | loanDTO.setBook(bookDTO);
111 | return loanDTO;
112 | }).collect(Collectors.toList());
113 | return new PageImpl(list, pageable, result.getTotalElements());
114 | }
115 |
116 | }
117 |
--------------------------------------------------------------------------------
/src/main/java/com/cursodsousa/libraryapi/api/resource/LoanController.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.api.resource;
2 |
3 | import com.cursodsousa.libraryapi.api.dto.BookDTO;
4 | import com.cursodsousa.libraryapi.api.dto.LoanDTO;
5 | import com.cursodsousa.libraryapi.api.dto.LoanFilterDTO;
6 | import com.cursodsousa.libraryapi.api.dto.ReturnedLoanDTO;
7 | import com.cursodsousa.libraryapi.exception.BusinessException;
8 | import com.cursodsousa.libraryapi.model.entity.Book;
9 | import com.cursodsousa.libraryapi.model.entity.Loan;
10 | import com.cursodsousa.libraryapi.service.BookService;
11 | import com.cursodsousa.libraryapi.service.LoanService;
12 | import lombok.RequiredArgsConstructor;
13 | import org.modelmapper.ModelMapper;
14 | import org.springframework.data.domain.Page;
15 | import org.springframework.data.domain.PageImpl;
16 | import org.springframework.data.domain.PageRequest;
17 | import org.springframework.data.domain.Pageable;
18 | import org.springframework.http.HttpStatus;
19 | import org.springframework.web.bind.annotation.*;
20 | import org.springframework.web.server.ResponseStatusException;
21 |
22 | import java.time.LocalDate;
23 | import java.util.List;
24 | import java.util.stream.Collectors;
25 |
26 | @RestController
27 | @RequestMapping("/api/loans")
28 | @RequiredArgsConstructor
29 | public class LoanController {
30 |
31 | private final LoanService service;
32 | private final BookService bookService;
33 | private final ModelMapper modelMapper;
34 |
35 | @PostMapping
36 | @ResponseStatus(HttpStatus.CREATED)
37 | public Long create(@RequestBody LoanDTO dto) {
38 | Book book = bookService
39 | .getBookByIsbn(dto.getIsbn())
40 | .orElseThrow(() ->
41 | new ResponseStatusException(HttpStatus.BAD_REQUEST, "Book not found for passed isbn"));
42 | Loan entity = Loan.builder()
43 | .book(book)
44 | .customer(dto.getCustomer())
45 | .loanDate(LocalDate.now())
46 | .build();
47 |
48 | entity = service.save(entity);
49 | return entity.getId();
50 | }
51 |
52 | @PatchMapping("{id}")
53 | public void returnBook(
54 | @PathVariable Long id,
55 | @RequestBody ReturnedLoanDTO dto) {
56 | Loan loan = service.getById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
57 | loan.setReturned(dto.getReturned());
58 | service.update(loan);
59 | }
60 |
61 | @GetMapping
62 | public Page find(LoanFilterDTO dto, Pageable pageRequest) {
63 | Page result = service.find(dto, pageRequest);
64 | List loans = result
65 | .getContent()
66 | .stream()
67 | .map(entity -> {
68 |
69 | Book book = entity.getBook();
70 | BookDTO bookDTO = modelMapper.map(book, BookDTO.class);
71 | LoanDTO loanDTO = modelMapper.map(entity, LoanDTO.class);
72 | loanDTO.setBook(bookDTO);
73 | return loanDTO;
74 |
75 | }).collect(Collectors.toList());
76 | return new PageImpl(loans, pageRequest, result.getTotalElements());
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/com/cursodsousa/libraryapi/config/SwaggerConfig.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.config;
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.Configuration;
5 | import springfox.documentation.builders.ApiInfoBuilder;
6 | import springfox.documentation.builders.PathSelectors;
7 | import springfox.documentation.builders.RequestHandlerSelectors;
8 | import springfox.documentation.service.ApiInfo;
9 | import springfox.documentation.service.Contact;
10 | import springfox.documentation.spi.DocumentationType;
11 | import springfox.documentation.spring.web.plugins.Docket;
12 | import springfox.documentation.swagger2.annotations.EnableSwagger2;
13 |
14 | @EnableSwagger2
15 | @Configuration
16 | public class SwaggerConfig {
17 |
18 | @Bean
19 | public Docket docket(){
20 | return new Docket(DocumentationType.SWAGGER_2)
21 | .select()
22 | .apis( RequestHandlerSelectors.basePackage("com.cursodsousa.libraryapi.api.resource") )
23 | .paths(PathSelectors.any())
24 | .build()
25 | .apiInfo(apiInfo());
26 | }
27 |
28 | private ApiInfo apiInfo(){
29 | return new ApiInfoBuilder()
30 | .title("Library API")
31 | .description("API do Projeto de controle de aluguel de livros")
32 | .version("1.0")
33 | .contact(contact())
34 | .build();
35 | }
36 |
37 | private Contact contact(){
38 | return new Contact("Dougllas Sousa",
39 | "http://github.com/dougllasfps",
40 | "cursodsousa@gmail.com");
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/cursodsousa/libraryapi/exception/BusinessException.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.exception;
2 |
3 | public class BusinessException extends RuntimeException {
4 | public BusinessException(String s) {
5 | super(s);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/cursodsousa/libraryapi/model/entity/Book.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.model.entity;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Builder;
5 | import lombok.Data;
6 | import lombok.NoArgsConstructor;
7 |
8 | import javax.persistence.*;
9 | import java.util.List;
10 |
11 | @Data
12 | @Builder
13 | @AllArgsConstructor
14 | @NoArgsConstructor
15 | @Entity
16 | @Table
17 | public class Book {
18 |
19 | @Id
20 | @Column
21 | @GeneratedValue(strategy = GenerationType.IDENTITY)
22 | private Long id;
23 |
24 | @Column
25 | private String title;
26 |
27 | @Column
28 | private String author;
29 |
30 | @Column
31 | private String isbn;
32 |
33 | @OneToMany( mappedBy = "book" )
34 | private List loans;
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/cursodsousa/libraryapi/model/entity/Loan.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.model.entity;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Builder;
5 | import lombok.Data;
6 | import lombok.NoArgsConstructor;
7 |
8 | import javax.persistence.*;
9 | import java.time.LocalDate;
10 |
11 | @Data
12 | @AllArgsConstructor
13 | @NoArgsConstructor
14 | @Builder
15 | @Entity
16 | public class Loan {
17 |
18 | @Id
19 | @Column
20 | @GeneratedValue(strategy = GenerationType.IDENTITY)
21 | private Long id;
22 |
23 | @Column(length = 100)
24 | private String customer;
25 |
26 | @Column(name = "customer_email")
27 | private String customerEmail;
28 |
29 | @JoinColumn(name = "id_book")
30 | @ManyToOne
31 | private Book book;
32 |
33 | @Column
34 | private LocalDate loanDate;
35 |
36 | @Column
37 | private Boolean returned;
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/com/cursodsousa/libraryapi/model/repository/BookRepository.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.model.repository;
2 |
3 | import com.cursodsousa.libraryapi.model.entity.Book;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 |
6 | import java.util.Optional;
7 |
8 | public interface BookRepository extends JpaRepository {
9 |
10 | boolean existsByIsbn( String isbn );
11 |
12 | Optional findByIsbn( String isbn);
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/cursodsousa/libraryapi/model/repository/LoanRepository.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.model.repository;
2 |
3 | import com.cursodsousa.libraryapi.model.entity.Book;
4 | import com.cursodsousa.libraryapi.model.entity.Loan;
5 | import org.springframework.data.domain.Page;
6 | import org.springframework.data.domain.PageRequest;
7 | import org.springframework.data.domain.Pageable;
8 | import org.springframework.data.jpa.repository.JpaRepository;
9 | import org.springframework.data.jpa.repository.Query;
10 | import org.springframework.data.repository.query.Param;
11 |
12 | import java.time.LocalDate;
13 | import java.util.List;
14 |
15 | public interface LoanRepository extends JpaRepository {
16 |
17 | @Query(value = " select case when ( count(l.id) > 0 ) then true else false end " +
18 | " from Loan l where l.book = :book and ( l.returned is null or l.returned is false ) ")
19 | boolean existsByBookAndNotReturned( @Param("book") Book book );
20 |
21 | @Query( value = " select l from Loan as l join l.book as b where b.isbn = :isbn or l.customer =:customer ")
22 | Page findByBookIsbnOrCustomer(
23 | @Param("isbn") String isbn,
24 | @Param("customer") String customer,
25 | Pageable pageable
26 | );
27 |
28 | Page findByBook( Book book, Pageable pageable );
29 |
30 | @Query(" select l from Loan l where l.loanDate <= :threeDaysAgo and ( l.returned is null or l.returned is false ) ")
31 | List findByLoanDateLessThanAndNotReturned( @Param("threeDaysAgo") LocalDate threeDaysAgo );
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/cursodsousa/libraryapi/service/BookService.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.service;
2 |
3 | import com.cursodsousa.libraryapi.api.dto.BookDTO;
4 | import com.cursodsousa.libraryapi.model.entity.Book;
5 | import org.springframework.data.domain.Page;
6 | import org.springframework.data.domain.Pageable;
7 |
8 | import java.util.List;
9 | import java.util.Optional;
10 |
11 | public interface BookService {
12 |
13 | Book save(Book any);
14 |
15 | Optional getById(Long id);
16 |
17 | void delete(Book book);
18 |
19 | Book update(Book book);
20 |
21 | Page find( Book filter, Pageable pageRequest );
22 |
23 | Optional getBookByIsbn(String isbn);
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/cursodsousa/libraryapi/service/EmailService.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.service;
2 |
3 | import java.util.List;
4 |
5 | public interface EmailService {
6 | void sendMails(String message, List mailsList);
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/cursodsousa/libraryapi/service/LoanService.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.service;
2 |
3 | import com.cursodsousa.libraryapi.api.dto.LoanFilterDTO;
4 | import com.cursodsousa.libraryapi.api.resource.BookController;
5 | import com.cursodsousa.libraryapi.model.entity.Book;
6 | import com.cursodsousa.libraryapi.model.entity.Loan;
7 | import org.springframework.data.domain.Page;
8 | import org.springframework.data.domain.Pageable;
9 |
10 | import java.util.List;
11 | import java.util.Optional;
12 |
13 | public interface LoanService {
14 |
15 | Loan save( Loan loan );
16 |
17 | Optional getById(Long id);
18 |
19 | Loan update(Loan loan);
20 |
21 | Page find(LoanFilterDTO filterDTO, Pageable pageable);
22 |
23 | Page getLoansByBook( Book book, Pageable pageable);
24 |
25 | List getAllLateLoans();
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/cursodsousa/libraryapi/service/ScheduleService.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.service;
2 |
3 | import com.cursodsousa.libraryapi.model.entity.Loan;
4 | import lombok.RequiredArgsConstructor;
5 | import org.springframework.beans.factory.annotation.Value;
6 | import org.springframework.scheduling.annotation.Scheduled;
7 | import org.springframework.stereotype.Service;
8 |
9 | import java.util.List;
10 | import java.util.stream.Collectors;
11 |
12 | @Service
13 | @RequiredArgsConstructor
14 | public class ScheduleService {
15 |
16 | private static final String CRON_LATE_LOANS = "0 0 0 1/1 * ?";
17 |
18 | @Value("${application.mail.lateloans.message}")
19 | private String message;
20 |
21 | private final LoanService loanService;
22 | private final EmailService emailService;
23 |
24 | @Scheduled(cron = CRON_LATE_LOANS)
25 | public void sendMailToLateLoans(){
26 | List allLateLoans = loanService.getAllLateLoans();
27 | List mailsList = allLateLoans.stream()
28 | .map(loan -> loan.getCustomerEmail())
29 | .collect(Collectors.toList());
30 |
31 | emailService.sendMails(message, mailsList);
32 |
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/cursodsousa/libraryapi/service/impl/BookServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.service.impl;
2 |
3 | import com.cursodsousa.libraryapi.exception.BusinessException;
4 | import com.cursodsousa.libraryapi.model.entity.Book;
5 | import com.cursodsousa.libraryapi.model.repository.BookRepository;
6 | import org.springframework.data.domain.*;
7 | import org.springframework.stereotype.Service;
8 |
9 | import java.util.Collections;
10 | import java.util.List;
11 | import java.util.Optional;
12 |
13 | @Service
14 | public class BookServiceImpl implements com.cursodsousa.libraryapi.service.BookService {
15 |
16 | private BookRepository repository;
17 |
18 | public BookServiceImpl(BookRepository repository) {
19 | this.repository = repository;
20 | }
21 |
22 | @Override
23 | public Book save(Book book) {
24 | if( repository.existsByIsbn(book.getIsbn()) ){
25 | throw new BusinessException("Isbn já cadastrado.");
26 | }
27 | return repository.save(book);
28 | }
29 |
30 | @Override
31 | public Optional getById(Long id) {
32 | return this.repository.findById(id);
33 | }
34 |
35 | @Override
36 | public void delete(Book book) {
37 | if(book == null || book.getId() == null){
38 | throw new IllegalArgumentException("Book id cant be null.");
39 | }
40 | this.repository.delete(book);
41 | }
42 |
43 | @Override
44 | public Book update(Book book) {
45 | if(book == null || book.getId() == null){
46 | throw new IllegalArgumentException("Book id cant be null.");
47 | }
48 | return this.repository.save(book);
49 | }
50 |
51 | @Override
52 | public Page find( Book filter, Pageable pageRequest ) {
53 | Example example = Example.of(filter,
54 | ExampleMatcher
55 | .matching()
56 | .withIgnoreCase()
57 | .withIgnoreNullValues()
58 | .withStringMatcher( ExampleMatcher.StringMatcher.CONTAINING )
59 | ) ;
60 | return repository.findAll(example, pageRequest);
61 | }
62 |
63 | @Override
64 | public Optional getBookByIsbn(String isbn) {
65 | return repository.findByIsbn(isbn);
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/com/cursodsousa/libraryapi/service/impl/EmailServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.service.impl;
2 |
3 | import com.cursodsousa.libraryapi.service.EmailService;
4 | import lombok.RequiredArgsConstructor;
5 | import org.springframework.beans.factory.annotation.Value;
6 | import org.springframework.mail.SimpleMailMessage;
7 | import org.springframework.mail.javamail.JavaMailSender;
8 | import org.springframework.stereotype.Service;
9 |
10 | import java.util.List;
11 |
12 | @Service
13 | @RequiredArgsConstructor
14 | public class EmailServiceImpl implements EmailService {
15 |
16 | @Value("${application.mail.default-remetent}")
17 | private String remetent;
18 |
19 | private final JavaMailSender javaMailSender;
20 |
21 | @Override
22 | public void sendMails(String message, List mailsList) {
23 |
24 | String[] mails = mailsList.toArray(new String[mailsList.size()]);
25 |
26 | SimpleMailMessage mailMessage = new SimpleMailMessage();
27 | mailMessage.setFrom(remetent);
28 | mailMessage.setSubject("Livro com empréstimo atrasado");
29 | mailMessage.setText(message);
30 | mailMessage.setTo(mails);
31 |
32 | javaMailSender.send(mailMessage);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/cursodsousa/libraryapi/service/impl/LoanServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.service.impl;
2 |
3 | import com.cursodsousa.libraryapi.api.dto.LoanFilterDTO;
4 | import com.cursodsousa.libraryapi.exception.BusinessException;
5 | import com.cursodsousa.libraryapi.model.entity.Book;
6 | import com.cursodsousa.libraryapi.model.entity.Loan;
7 | import com.cursodsousa.libraryapi.model.repository.LoanRepository;
8 | import com.cursodsousa.libraryapi.service.LoanService;
9 | import org.springframework.data.domain.Page;
10 | import org.springframework.data.domain.Pageable;
11 | import org.springframework.stereotype.Service;
12 |
13 | import java.time.LocalDate;
14 | import java.util.List;
15 | import java.util.Optional;
16 |
17 | @Service
18 | public class LoanServiceImpl implements LoanService {
19 |
20 | private LoanRepository repository;
21 |
22 | public LoanServiceImpl(LoanRepository repository) {
23 | this.repository = repository;
24 | }
25 |
26 | @Override
27 | public Loan save( Loan loan ) {
28 | if( repository.existsByBookAndNotReturned(loan.getBook()) ){
29 | throw new BusinessException("Book already loaned");
30 | }
31 | return repository.save(loan);
32 | }
33 |
34 | @Override
35 | public Optional getById(Long id) {
36 | return repository.findById(id);
37 | }
38 |
39 | @Override
40 | public Loan update(Loan loan) {
41 | return repository.save(loan);
42 | }
43 |
44 | @Override
45 | public Page find(LoanFilterDTO filterDTO, Pageable pageable) {
46 | return repository.findByBookIsbnOrCustomer( filterDTO.getIsbn(), filterDTO.getCustomer(), pageable );
47 | }
48 |
49 | @Override
50 | public Page getLoansByBook(Book book, Pageable pageable) {
51 | return repository.findByBook(book, pageable);
52 | }
53 |
54 | @Override
55 | public List getAllLateLoans() {
56 | final Integer loanDays = 4;
57 | LocalDate threeDaysAgo = LocalDate.now().minusDays(loanDays);
58 | return repository.findByLoanDateLessThanAndNotReturned(threeDaysAgo);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dougllasfps/library-api/6c4307910feeefdb4455f4df0ca4ddbbd3a9060b/src/main/resources/application.properties
--------------------------------------------------------------------------------
/src/test/java/com/cursodsousa/libraryapi/LibraryApiApplicationTests.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.springframework.boot.test.context.SpringBootTest;
5 |
6 | @SpringBootTest
7 | class LibraryApiApplicationTests {
8 |
9 | @Test
10 | void contextLoads() {
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/test/java/com/cursodsousa/libraryapi/api/resource/BookControllerTest.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.api.resource;
2 |
3 | import com.cursodsousa.libraryapi.api.dto.BookDTO;
4 | import com.cursodsousa.libraryapi.exception.BusinessException;
5 | import com.cursodsousa.libraryapi.model.entity.Book;
6 | import com.cursodsousa.libraryapi.service.BookService;
7 | import com.cursodsousa.libraryapi.service.LoanService;
8 | import com.fasterxml.jackson.databind.ObjectMapper;
9 | import org.hamcrest.Matchers;
10 | import org.junit.jupiter.api.DisplayName;
11 | import org.junit.jupiter.api.Test;
12 | import org.junit.jupiter.api.extension.ExtendWith;
13 | import org.mockito.BDDMockito;
14 | import org.mockito.Mockito;
15 | import org.springframework.beans.factory.annotation.Autowired;
16 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
17 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
18 | import org.springframework.boot.test.mock.mockito.MockBean;
19 | import org.springframework.data.domain.PageImpl;
20 | import org.springframework.data.domain.PageRequest;
21 | import org.springframework.data.domain.Pageable;
22 | import org.springframework.http.MediaType;
23 | import org.springframework.test.context.ActiveProfiles;
24 | import org.springframework.test.context.junit.jupiter.SpringExtension;
25 | import org.springframework.test.web.servlet.MockMvc;
26 | import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
27 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
28 |
29 | import java.util.Arrays;
30 | import java.util.Collections;
31 | import java.util.Optional;
32 |
33 | import static org.hamcrest.Matchers.hasSize;
34 | import static org.mockito.ArgumentMatchers.anyLong;
35 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
36 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
37 |
38 | @ExtendWith(SpringExtension.class)
39 | @ActiveProfiles("test")
40 | @WebMvcTest(controllers = BookController.class)
41 | @AutoConfigureMockMvc
42 | public class BookControllerTest {
43 |
44 | static String BOOK_API = "/api/books";
45 |
46 | @Autowired
47 | MockMvc mvc;
48 |
49 | @MockBean
50 | BookService service;
51 |
52 | @MockBean
53 | LoanService loanService;
54 |
55 | @Test
56 | @DisplayName("Deve criar um livro com sucesso.")
57 | public void createBookTest() throws Exception {
58 |
59 | BookDTO dto = createNewBook();
60 | Book savedBook = Book.builder().id(10l).author("Artur").title("As aventuras").isbn("001").build();
61 |
62 | BDDMockito.given(service.save(Mockito.any(Book.class))).willReturn(savedBook);
63 | String json = new ObjectMapper().writeValueAsString(dto);
64 |
65 | MockHttpServletRequestBuilder request = MockMvcRequestBuilders
66 | .post(BOOK_API)
67 | .contentType(MediaType.APPLICATION_JSON)
68 | .accept(MediaType.APPLICATION_JSON)
69 | .content(json);
70 |
71 | mvc
72 | .perform(request)
73 | .andExpect( status().isCreated() )
74 | .andExpect( jsonPath("id").value(10l) )
75 | .andExpect( jsonPath("title").value(dto.getTitle()) )
76 | .andExpect( jsonPath("author").value(dto.getAuthor()) )
77 | .andExpect( jsonPath("isbn").value(dto.getIsbn()) )
78 |
79 | ;
80 |
81 | }
82 |
83 | @Test
84 | @DisplayName("Deve lançar erro de validação quando não houver dados suficiente para criação do livro.")
85 | public void createInvalidBookTest() throws Exception {
86 |
87 | String json = new ObjectMapper().writeValueAsString(new BookDTO());
88 |
89 | MockHttpServletRequestBuilder request = MockMvcRequestBuilders
90 | .post(BOOK_API)
91 | .contentType(MediaType.APPLICATION_JSON)
92 | .accept(MediaType.APPLICATION_JSON)
93 | .content(json);
94 |
95 | mvc.perform(request)
96 | .andExpect( status().isBadRequest() )
97 | .andExpect( jsonPath("errors", hasSize(3)));
98 | }
99 |
100 | @Test
101 | @DisplayName("Deve lançar erro ao tentar cadastrar um livro com isbn já utilizado por outro.")
102 | public void createBookWithDuplicatedIsbn() throws Exception {
103 |
104 | BookDTO dto = createNewBook();
105 | String json = new ObjectMapper().writeValueAsString(dto);
106 | String mensagemErro = "Isbn já cadastrado.";
107 | BDDMockito.given(service.save(Mockito.any(Book.class)))
108 | .willThrow(new BusinessException(mensagemErro));
109 |
110 | MockHttpServletRequestBuilder request = MockMvcRequestBuilders
111 | .post(BOOK_API)
112 | .contentType(MediaType.APPLICATION_JSON)
113 | .accept(MediaType.APPLICATION_JSON)
114 | .content(json);
115 |
116 | mvc.perform( request )
117 | .andExpect(status().isBadRequest())
118 | .andExpect(jsonPath("errors", hasSize(1)))
119 | .andExpect(jsonPath("errors[0]").value(mensagemErro));
120 |
121 | }
122 |
123 | @Test
124 | @DisplayName("Deve obter informacoes de um livro.")
125 | public void getBookDetailsTest() throws Exception{
126 | //cenario (given)
127 | Long id = 1l;
128 |
129 | Book book = Book.builder()
130 | .id(id)
131 | .title(createNewBook().getTitle())
132 | .author(createNewBook().getAuthor())
133 | .isbn(createNewBook().getIsbn())
134 | .build();
135 |
136 | BDDMockito.given( service.getById(id) ).willReturn(Optional.of(book));
137 |
138 | //execucao (when)
139 | MockHttpServletRequestBuilder request = MockMvcRequestBuilders
140 | .get(BOOK_API.concat("/" + id))
141 | .accept(MediaType.APPLICATION_JSON);
142 |
143 | mvc
144 | .perform(request)
145 | .andExpect(status().isOk())
146 | .andExpect( jsonPath("id").value(id) )
147 | .andExpect( jsonPath("title").value(createNewBook().getTitle()) )
148 | .andExpect( jsonPath("author").value(createNewBook().getAuthor()) )
149 | .andExpect( jsonPath("isbn").value(createNewBook().getIsbn()) )
150 | ;
151 | }
152 |
153 | @Test
154 | @DisplayName("Deve retornar resource not found quando o livro procurado não existir")
155 | public void bookNotFoundTest() throws Exception {
156 |
157 | BDDMockito.given( service.getById(Mockito.anyLong()) ).willReturn( Optional.empty() );
158 |
159 | MockHttpServletRequestBuilder request = MockMvcRequestBuilders
160 | .get(BOOK_API.concat("/" + 1))
161 | .accept(MediaType.APPLICATION_JSON);
162 |
163 | mvc
164 | .perform(request)
165 | .andExpect(status().isNotFound());
166 | }
167 |
168 | @Test
169 | @DisplayName("Deve deletar um livro")
170 | public void deleteBookTest() throws Exception {
171 |
172 | BDDMockito.given(service.getById(anyLong())).willReturn(Optional.of(Book.builder().id(1l).build()));
173 |
174 | MockHttpServletRequestBuilder request = MockMvcRequestBuilders
175 | .delete(BOOK_API.concat("/" + 1));
176 |
177 | mvc.perform( request )
178 | .andExpect( status().isNoContent() );
179 | }
180 |
181 | @Test
182 | @DisplayName("Deve retornar resource not found quando não encontrar o livro para deletar")
183 | public void deleteInexistentBookTest() throws Exception {
184 |
185 | BDDMockito.given(service.getById(anyLong())).willReturn(Optional.empty());
186 |
187 | MockHttpServletRequestBuilder request = MockMvcRequestBuilders
188 | .delete(BOOK_API.concat("/" + 1));
189 |
190 | mvc.perform( request )
191 | .andExpect( status().isNotFound() );
192 | }
193 |
194 | @Test
195 | @DisplayName("Deve atualizar um livro")
196 | public void updateBookTest() throws Exception {
197 | Long id = 1l;
198 | String json = new ObjectMapper().writeValueAsString(createNewBook());
199 |
200 | Book updatingBook = Book.builder().id(1l).title("some title").author("some author").isbn("321").build();
201 | BDDMockito.given( service.getById(id) ).willReturn( Optional.of(updatingBook) );
202 | Book updatedBook = Book.builder().id(id).author("Artur").title("As aventuras").isbn("321").build();
203 | BDDMockito.given(service.update(updatingBook)).willReturn(updatedBook);
204 |
205 | MockHttpServletRequestBuilder request = MockMvcRequestBuilders
206 | .put(BOOK_API.concat("/" + 1))
207 | .content(json)
208 | .accept(MediaType.APPLICATION_JSON)
209 | .contentType(MediaType.APPLICATION_JSON);
210 |
211 | mvc.perform( request )
212 | .andExpect( status().isOk() )
213 | .andExpect( jsonPath("id").value(id) )
214 | .andExpect( jsonPath("title").value(createNewBook().getTitle()) )
215 | .andExpect( jsonPath("author").value(createNewBook().getAuthor()) )
216 | .andExpect( jsonPath("isbn").value("321") );
217 | }
218 |
219 | @Test
220 | @DisplayName("Deve retornar 404 ao tentar atualizar um livro inexistente")
221 | public void updateInexistentBookTest() throws Exception {
222 |
223 | String json = new ObjectMapper().writeValueAsString(createNewBook());
224 | BDDMockito.given( service.getById(Mockito.anyLong()) )
225 | .willReturn( Optional.empty() );
226 |
227 | MockHttpServletRequestBuilder request = MockMvcRequestBuilders
228 | .put(BOOK_API.concat("/" + 1))
229 | .content(json)
230 | .accept(MediaType.APPLICATION_JSON)
231 | .contentType(MediaType.APPLICATION_JSON);
232 |
233 | mvc.perform( request )
234 | .andExpect( status().isNotFound() );
235 | }
236 |
237 | @Test
238 | @DisplayName("Deve filtrar livros")
239 | public void findBooksTest() throws Exception{
240 |
241 | Long id = 1l;
242 |
243 | Book book = Book.builder()
244 | .id(id)
245 | .title(createNewBook().getTitle())
246 | .author(createNewBook().getAuthor())
247 | .isbn(createNewBook().getIsbn())
248 | .build();
249 |
250 | BDDMockito.given( service.find(Mockito.any(Book.class), Mockito.any(Pageable.class)) )
251 | .willReturn( new PageImpl( Arrays.asList(book), PageRequest.of(0,100), 1 ) );
252 |
253 | String queryString = String.format("?title=%s&author=%s&page=0&size=100",
254 | book.getTitle(), book.getAuthor());
255 |
256 | MockHttpServletRequestBuilder request = MockMvcRequestBuilders
257 | .get(BOOK_API.concat(queryString))
258 | .accept(MediaType.APPLICATION_JSON);
259 |
260 | mvc
261 | .perform( request )
262 | .andExpect( status().isOk() )
263 | .andExpect( jsonPath("content", Matchers.hasSize(1)))
264 | .andExpect( jsonPath("totalElements").value(1) )
265 | .andExpect( jsonPath("pageable.pageSize").value(100) )
266 | .andExpect( jsonPath("pageable.pageNumber").value(0))
267 | ;
268 | }
269 |
270 | private BookDTO createNewBook() {
271 | return BookDTO.builder().author("Artur").title("As aventuras").isbn("001").build();
272 | }
273 | }
274 |
--------------------------------------------------------------------------------
/src/test/java/com/cursodsousa/libraryapi/api/resource/LoanControllerTest.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.api.resource;
2 |
3 | import com.cursodsousa.libraryapi.api.dto.LoanDTO;
4 | import com.cursodsousa.libraryapi.api.dto.LoanFilterDTO;
5 | import com.cursodsousa.libraryapi.api.dto.ReturnedLoanDTO;
6 | import com.cursodsousa.libraryapi.exception.BusinessException;
7 | import com.cursodsousa.libraryapi.model.entity.Book;
8 | import com.cursodsousa.libraryapi.model.entity.Loan;
9 | import com.cursodsousa.libraryapi.service.BookService;
10 | import com.cursodsousa.libraryapi.service.LoanService;
11 | import com.cursodsousa.libraryapi.service.LoanServiceTest;
12 | import com.fasterxml.jackson.databind.ObjectMapper;
13 | import org.hamcrest.Matchers;
14 | import org.junit.jupiter.api.DisplayName;
15 | import org.junit.jupiter.api.Test;
16 | import org.junit.jupiter.api.extension.ExtendWith;
17 | import org.mockito.BDDMockito;
18 | import org.mockito.Mock;
19 | import org.mockito.Mockito;
20 | import org.springframework.beans.factory.annotation.Autowired;
21 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
22 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
23 | import org.springframework.boot.test.mock.mockito.MockBean;
24 | import org.springframework.data.domain.PageImpl;
25 | import org.springframework.data.domain.PageRequest;
26 | import org.springframework.data.domain.Pageable;
27 | import org.springframework.http.MediaType;
28 | import org.springframework.test.context.ActiveProfiles;
29 | import org.springframework.test.context.junit.jupiter.SpringExtension;
30 | import org.springframework.test.web.servlet.MockMvc;
31 | import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
32 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
33 |
34 | import java.time.LocalDate;
35 | import java.util.Arrays;
36 | import java.util.Optional;
37 |
38 | import static com.cursodsousa.libraryapi.api.resource.BookControllerTest.BOOK_API;
39 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
40 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
41 |
42 | @ExtendWith(SpringExtension.class)
43 | @ActiveProfiles("test")
44 | @AutoConfigureMockMvc
45 | @WebMvcTest(controllers = LoanController.class)
46 | public class LoanControllerTest {
47 |
48 | static final String LOAN_API = "/api/loans";
49 |
50 | @Autowired
51 | MockMvc mvc;
52 |
53 | @MockBean
54 | private BookService bookService;
55 |
56 | @MockBean
57 | private LoanService loanService;
58 |
59 | @Test
60 | @DisplayName("Deve realizar um emprestimo")
61 | public void createLoanTest() throws Exception {
62 |
63 | LoanDTO dto = LoanDTO.builder().isbn("123").email("customer@email.com").customer("Fulano").build();
64 | String json = new ObjectMapper().writeValueAsString(dto);
65 |
66 | Book book = Book.builder().id(1l).isbn("123").build();
67 | BDDMockito.given( bookService.getBookByIsbn("123") ).willReturn(Optional.of(book) );
68 |
69 | Loan loan = Loan.builder().id(1l).customer("Fulano").book(book).loanDate(LocalDate.now()).build();
70 | BDDMockito.given( loanService.save(Mockito.any(Loan.class)) ).willReturn(loan);
71 |
72 | MockHttpServletRequestBuilder request = MockMvcRequestBuilders.post(LOAN_API)
73 | .accept(MediaType.APPLICATION_JSON)
74 | .contentType(MediaType.APPLICATION_JSON)
75 | .content(json);
76 |
77 | mvc.perform( request )
78 | .andExpect( status().isCreated() )
79 | .andExpect( content().string("1") )
80 | ;
81 |
82 | }
83 |
84 | @Test
85 | @DisplayName("Deve retornar erro ao tentar fazer emprestimo de um livro inexistente.")
86 | public void invalidIsbnCreateLoanTest() throws Exception{
87 |
88 | LoanDTO dto = LoanDTO.builder().isbn("123").customer("Fulano").build();
89 | String json = new ObjectMapper().writeValueAsString(dto);
90 |
91 | BDDMockito.given( bookService.getBookByIsbn("123") ).willReturn( Optional.empty() );
92 |
93 | MockHttpServletRequestBuilder request = MockMvcRequestBuilders.post(LOAN_API)
94 | .accept(MediaType.APPLICATION_JSON)
95 | .contentType(MediaType.APPLICATION_JSON)
96 | .content(json);
97 |
98 | mvc.perform( request )
99 | .andExpect( status().isBadRequest() )
100 | .andExpect( jsonPath("errors", Matchers.hasSize(1)) )
101 | .andExpect( jsonPath("errors[0]").value("Book not found for passed isbn"))
102 | ;
103 | }
104 |
105 | @Test
106 | @DisplayName("Deve retornar erro ao tentar fazer emprestimo de um livro emprestado.")
107 | public void loanedBookErrorOnCreateLoanTest() throws Exception{
108 |
109 | LoanDTO dto = LoanDTO.builder().isbn("123").customer("Fulano").build();
110 | String json = new ObjectMapper().writeValueAsString(dto);
111 |
112 | Book book = Book.builder().id(1l).isbn("123").build();
113 | BDDMockito.given( bookService.getBookByIsbn("123") ).willReturn(Optional.of(book) );
114 |
115 | BDDMockito.given( loanService.save(Mockito.any(Loan.class)) )
116 | .willThrow(new BusinessException("Book already loaned"));
117 |
118 | MockHttpServletRequestBuilder request = MockMvcRequestBuilders.post(LOAN_API)
119 | .accept(MediaType.APPLICATION_JSON)
120 | .contentType(MediaType.APPLICATION_JSON)
121 | .content(json);
122 |
123 | mvc.perform( request )
124 | .andExpect( status().isBadRequest() )
125 | .andExpect( jsonPath("errors", Matchers.hasSize(1)) )
126 | .andExpect( jsonPath("errors[0]").value("Book already loaned"))
127 | ;
128 | }
129 |
130 | @Test
131 | @DisplayName("Deve retornar um livro")
132 | public void returnBookTest() throws Exception{
133 | //cenário { returned: true }
134 | ReturnedLoanDTO dto = ReturnedLoanDTO.builder().returned(true).build();
135 | Loan loan = Loan.builder().id(1l).build();
136 | BDDMockito.given(loanService.getById(Mockito.anyLong()))
137 | .willReturn(Optional.of(loan));
138 |
139 | String json = new ObjectMapper().writeValueAsString(dto);
140 |
141 | mvc.perform(
142 | patch(LOAN_API.concat("/1"))
143 | .accept(MediaType.APPLICATION_JSON)
144 | .contentType(MediaType.APPLICATION_JSON)
145 | .content(json)
146 | ).andExpect( status().isOk() );
147 |
148 | Mockito.verify(loanService, Mockito.times(1)).update(loan);
149 |
150 | }
151 |
152 | @Test
153 | @DisplayName("Deve retornar 404 quando tentar devolver um livro inexistente.")
154 | public void returnInexistentBookTest() throws Exception{
155 | //cenário
156 | ReturnedLoanDTO dto = ReturnedLoanDTO.builder().returned(true).build();
157 | String json = new ObjectMapper().writeValueAsString(dto);
158 |
159 | BDDMockito.given(loanService.getById(Mockito.anyLong())).willReturn(Optional.empty());
160 |
161 | mvc.perform(
162 | patch(LOAN_API.concat("/1"))
163 | .accept(MediaType.APPLICATION_JSON)
164 | .contentType(MediaType.APPLICATION_JSON)
165 | .content(json)
166 | ).andExpect( status().isNotFound() );
167 |
168 | }
169 |
170 | @Test
171 | @DisplayName("Deve filtrar empréstimos")
172 | public void findLoansTest() throws Exception{
173 | //cenário
174 | Long id = 1l;
175 | Loan loan = LoanServiceTest.createLoan();
176 | loan.setId(id);
177 | Book book = Book.builder().id(1l).isbn("321").build();
178 | loan.setBook(book);
179 |
180 | BDDMockito.given( loanService.find( Mockito.any(LoanFilterDTO.class), Mockito.any(Pageable.class)) )
181 | .willReturn( new PageImpl( Arrays.asList(loan), PageRequest.of(0,10), 1 ) );
182 |
183 | String queryString = String.format("?isbn=%s&customer=%s&page=0&size=10",
184 | book.getIsbn(), loan.getCustomer());
185 |
186 | MockHttpServletRequestBuilder request = MockMvcRequestBuilders
187 | .get(LOAN_API.concat(queryString))
188 | .accept(MediaType.APPLICATION_JSON);
189 |
190 | mvc
191 | .perform( request )
192 | .andExpect( status().isOk() )
193 | .andExpect( jsonPath("content", Matchers.hasSize(1)))
194 | .andExpect( jsonPath("totalElements").value(1) )
195 | .andExpect( jsonPath("pageable.pageSize").value(10) )
196 | .andExpect( jsonPath("pageable.pageNumber").value(0))
197 | ;
198 | }
199 |
200 | }
201 |
--------------------------------------------------------------------------------
/src/test/java/com/cursodsousa/libraryapi/model/repository/BookRepositoryTest.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.model.repository;
2 |
3 | import com.cursodsousa.libraryapi.model.entity.Book;
4 | import org.junit.jupiter.api.DisplayName;
5 | import org.junit.jupiter.api.Test;
6 | import org.junit.jupiter.api.extension.ExtendWith;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
9 | import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
10 | import org.springframework.test.context.ActiveProfiles;
11 | import org.springframework.test.context.junit.jupiter.SpringExtension;
12 |
13 | import java.util.Optional;
14 |
15 | import static org.assertj.core.api.Assertions.assertThat;
16 |
17 | @ExtendWith(SpringExtension.class)
18 | @ActiveProfiles("test")
19 | @DataJpaTest
20 | public class BookRepositoryTest {
21 |
22 | @Autowired
23 | TestEntityManager entityManager;
24 |
25 | @Autowired
26 | BookRepository repository;
27 |
28 | @Test
29 | @DisplayName("Deve retornar verdadeiro quando existir um livro na base com o isbn informado")
30 | public void returnTrueWhenIsbnExists(){
31 | //cenario
32 | String isbn = "123";
33 | Book book = createNewBook(isbn);
34 | entityManager.persist(book);
35 |
36 | //execucao
37 | boolean exists = repository.existsByIsbn(isbn);
38 |
39 | //verificacao
40 | assertThat(exists).isTrue();
41 | }
42 |
43 | public static Book createNewBook(String isbn) {
44 | return Book.builder().title("Aventuras").author("Fulano").isbn(isbn).build();
45 | }
46 |
47 | @Test
48 | @DisplayName("Deve retornar false quando não existir um livro na base com o isbn informado")
49 | public void returnFalseWhenIsbnDoesntExist(){
50 | //cenario
51 | String isbn = "123";
52 | //execucao
53 | boolean exists = repository.existsByIsbn(isbn);
54 |
55 | //verificacao
56 | assertThat(exists).isFalse();
57 | }
58 |
59 | @Test
60 | @DisplayName("Deve obter um livro por id.")
61 | public void findByIdTest(){
62 | //cenário
63 | Book book = createNewBook("123");
64 | entityManager.persist(book);
65 |
66 | //execucao
67 | Optional foundBook = repository.findById(book.getId());
68 |
69 | //verificacoes
70 | assertThat(foundBook.isPresent()).isTrue();
71 | }
72 |
73 | @Test
74 | @DisplayName("Deve salvar um livro.")
75 | public void saveBookTest(){
76 |
77 | Book book = createNewBook("123");
78 |
79 | Book savedBook = repository.save(book);
80 |
81 | assertThat(savedBook.getId()).isNotNull();
82 |
83 | }
84 |
85 | @Test
86 | @DisplayName("Deve deletar um livro")
87 | public void deleteBookTest(){
88 |
89 | Book book = createNewBook("123");
90 | entityManager.persist(book);
91 | Book foundBook = entityManager.find( Book.class, book.getId() );
92 |
93 | repository.delete(foundBook);
94 |
95 | Book deletedBook = entityManager.find(Book.class, book.getId());
96 | assertThat(deletedBook).isNull();
97 |
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/src/test/java/com/cursodsousa/libraryapi/model/repository/LoanRepositoryTest.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.model.repository;
2 |
3 | import com.cursodsousa.libraryapi.model.entity.Book;
4 | import com.cursodsousa.libraryapi.model.entity.Loan;
5 | import org.junit.jupiter.api.DisplayName;
6 | import org.junit.jupiter.api.Test;
7 | import org.junit.jupiter.api.extension.ExtendWith;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
10 | import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
11 | import org.springframework.data.domain.Page;
12 | import org.springframework.data.domain.PageRequest;
13 | import org.springframework.test.context.ActiveProfiles;
14 | import org.springframework.test.context.junit.jupiter.SpringExtension;
15 |
16 | import javax.persistence.EntityManager;
17 |
18 | import java.time.LocalDate;
19 | import java.util.List;
20 |
21 | import static com.cursodsousa.libraryapi.model.repository.BookRepositoryTest.createNewBook;
22 | import static org.assertj.core.api.Assertions.assertThat;
23 |
24 | @ExtendWith(SpringExtension.class)
25 | @ActiveProfiles("test")
26 | @DataJpaTest
27 | public class LoanRepositoryTest {
28 |
29 | @Autowired
30 | private LoanRepository repository;
31 |
32 | @Autowired
33 | private TestEntityManager entityManager;
34 |
35 | @Test
36 | @DisplayName("deve verificar se existe empréstimo não devolvido para o livro.")
37 | public void existsByBookAndNotReturnedTest(){
38 | //cenário
39 | Loan loan = createAndPersistLoan(LocalDate.now());
40 | Book book = loan.getBook();
41 |
42 | //execucao
43 | boolean exists = repository.existsByBookAndNotReturned(book);
44 |
45 | assertThat(exists).isTrue();
46 | }
47 |
48 | @Test
49 | @DisplayName("Deve buscar empréstimo pelo isbn do livro ou customer")
50 | public void findByBookIsbnOrCustomerTest(){
51 | Loan loan = createAndPersistLoan(LocalDate.now());
52 |
53 | Page result = repository.findByBookIsbnOrCustomer(
54 | "123", "Fulano", PageRequest.of(0, 10));
55 |
56 | assertThat(result.getContent()).hasSize(1);
57 | assertThat(result.getContent()).contains(loan);
58 | assertThat(result.getPageable().getPageSize()).isEqualTo(10);
59 | assertThat(result.getPageable().getPageNumber()).isEqualTo(0);
60 | assertThat(result.getTotalElements()).isEqualTo(1);
61 | }
62 |
63 | @Test
64 | @DisplayName("Deve obter empréstimos cuja data emprestimo for menor ou igual a tres dias atras e nao retornados")
65 | public void findByLoanDateLessThanAndNotReturnedTest(){
66 | Loan loan = createAndPersistLoan( LocalDate.now().minusDays(5) );
67 |
68 | List result = repository.findByLoanDateLessThanAndNotReturned(LocalDate.now().minusDays(4));
69 |
70 | assertThat(result).hasSize(1).contains(loan);
71 | }
72 |
73 | @Test
74 | @DisplayName("Deve retornar vazio quando não houver emprestimos atrasados.")
75 | public void notFindByLoanDateLessThanAndNotReturnedTest(){
76 | Loan loan = createAndPersistLoan( LocalDate.now());
77 |
78 | List result = repository.findByLoanDateLessThanAndNotReturned(LocalDate.now().minusDays(4));
79 |
80 | assertThat(result).isEmpty();
81 | }
82 |
83 | public Loan createAndPersistLoan(LocalDate loanDate){
84 | Book book = createNewBook("123");
85 | entityManager.persist(book);
86 |
87 | Loan loan = Loan.builder().book(book).customer("Fulano").loanDate(loanDate).build();
88 | entityManager.persist(loan);
89 |
90 | return loan;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/test/java/com/cursodsousa/libraryapi/service/BookServiceTest.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.service;
2 |
3 | import com.cursodsousa.libraryapi.exception.BusinessException;
4 | import com.cursodsousa.libraryapi.model.entity.Book;
5 | import com.cursodsousa.libraryapi.model.repository.BookRepository;
6 | import com.cursodsousa.libraryapi.service.impl.BookServiceImpl;
7 | import org.assertj.core.api.Assertions;
8 | import org.junit.jupiter.api.BeforeEach;
9 | import org.junit.jupiter.api.DisplayName;
10 | import org.junit.jupiter.api.Test;
11 | import org.junit.jupiter.api.extension.ExtendWith;
12 | import org.mockito.Mockito;
13 | import org.springframework.boot.test.mock.mockito.MockBean;
14 | import org.springframework.boot.test.mock.mockito.SpyBean;
15 | import org.springframework.data.domain.Example;
16 | import org.springframework.data.domain.Page;
17 | import org.springframework.data.domain.PageImpl;
18 | import org.springframework.data.domain.PageRequest;
19 | import org.springframework.test.context.ActiveProfiles;
20 | import org.springframework.test.context.junit.jupiter.SpringExtension;
21 |
22 | import java.util.Arrays;
23 | import java.util.List;
24 | import java.util.Optional;
25 |
26 | import static org.assertj.core.api.Assertions.assertThat;
27 | import static org.mockito.Mockito.*;
28 |
29 | @ExtendWith(SpringExtension.class)
30 | @ActiveProfiles("test")
31 | public class BookServiceTest {
32 |
33 | BookService service;
34 |
35 | @MockBean
36 | BookRepository repository;
37 |
38 | @BeforeEach
39 | public void setUp() {
40 | this.service = new BookServiceImpl(repository);
41 | }
42 |
43 | @Test
44 | @DisplayName("Deve salvar um livro")
45 | public void saveBookTest() {
46 | //cenario
47 | Book book = createValidBook();
48 | when(repository.existsByIsbn(Mockito.anyString()) ).thenReturn(false);
49 | when(repository.save(book)).thenReturn(
50 | Book.builder().id(1l)
51 | .isbn("123")
52 | .author("Fulano")
53 | .title("As aventuras").build()
54 | );
55 |
56 | //execucao
57 | Book savedBook = service.save(book);
58 |
59 | //verificacao
60 | assertThat(savedBook.getId()).isNotNull();
61 | assertThat(savedBook.getIsbn()).isEqualTo("123");
62 | assertThat(savedBook.getTitle()).isEqualTo("As aventuras");
63 | assertThat(savedBook.getAuthor()).isEqualTo("Fulano");
64 | }
65 |
66 | private Book createValidBook() {
67 | return Book.builder().isbn("123").author("Fulano").title("As aventuras").build();
68 | }
69 |
70 | @Test
71 | @DisplayName("Deve lançar erro de negocio ao tentar salvar um livro com isbn duplicado")
72 | public void shouldNotSaveABookWithDuplicatedISBN(){
73 | //cenario
74 | Book book = createValidBook();
75 | when( repository.existsByIsbn(Mockito.anyString()) ).thenReturn(true);
76 |
77 | //execucao
78 | Throwable exception = Assertions.catchThrowable(() -> service.save(book));
79 |
80 | //verificacoes
81 | assertThat(exception)
82 | .isInstanceOf(BusinessException.class)
83 | .hasMessage("Isbn já cadastrado.");
84 |
85 | Mockito.verify(repository, Mockito.never()).save(book);
86 |
87 | }
88 |
89 | @Test
90 | @DisplayName("Deve obter um livro por Id")
91 | public void getByIdTest(){
92 | Long id = 1l;
93 | Book book = createValidBook();
94 | book.setId(id);
95 |
96 | when(repository.findById(id)).thenReturn(Optional.of(book));
97 |
98 | //execucao
99 | Optional foundBook = service.getById(id);
100 |
101 | //verificacoes
102 | assertThat( foundBook.isPresent() ).isTrue();
103 | assertThat( foundBook.get().getId()).isEqualTo(id);
104 | assertThat( foundBook.get().getAuthor()).isEqualTo(book.getAuthor());
105 | assertThat( foundBook.get().getIsbn()).isEqualTo(book.getIsbn());
106 | assertThat( foundBook.get().getTitle()).isEqualTo(book.getTitle());
107 | }
108 |
109 | @Test
110 | @DisplayName("Deve retornar vazio ao obter um livro por Id quando ele não existe na base.")
111 | public void bookNotFoundByIdTest(){
112 | Long id = 1l;
113 | when( repository.findById(id) ).thenReturn(Optional.empty());
114 |
115 | //execucao
116 | Optional book = service.getById(id);
117 |
118 | //verificacoes
119 | assertThat( book.isPresent() ).isFalse();
120 |
121 | }
122 |
123 | @Test
124 | @DisplayName("Deve deletar um livro.")
125 | public void deleteBookTest(){
126 | Book book = Book.builder().id(1l).build();
127 |
128 | //execucao
129 | org.junit.jupiter.api.Assertions.assertDoesNotThrow( () -> service.delete(book) );
130 |
131 | //verificacoes
132 | Mockito.verify(repository, Mockito.times(1)).delete(book);
133 | }
134 |
135 | @Test
136 | @DisplayName("Deve ocorrer erro ao tentar deletar um livro inexistente.")
137 | public void deleteInvalidBookTest(){
138 | Book book = new Book();
139 |
140 | org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class, () -> service.delete(book));
141 |
142 | Mockito.verify( repository, Mockito.never() ).delete(book);
143 | }
144 |
145 | @Test
146 | @DisplayName("Deve ocorrer erro ao tentar atualizar um livro inexistente.")
147 | public void updateInvalidBookTest(){
148 | Book book = new Book();
149 |
150 | org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class, () -> service.update(book));
151 |
152 | Mockito.verify( repository, Mockito.never() ).save(book);
153 | }
154 |
155 | @Test
156 | @DisplayName("Deve atualizar um livro.")
157 | public void updateBookTest(){
158 | //cenário
159 | long id = 1l;
160 |
161 | //livro a atualizar
162 | Book updatingBook = Book.builder().id(id).build();
163 |
164 | //simulacao
165 | Book updatedBook = createValidBook();
166 | updatedBook.setId(id);
167 | when(repository.save(updatingBook)).thenReturn(updatedBook);
168 |
169 | //exeucao
170 | Book book = service.update(updatingBook);
171 |
172 | //verificacoes
173 | assertThat(book.getId()).isEqualTo(updatedBook.getId());
174 | assertThat(book.getTitle()).isEqualTo(updatedBook.getTitle());
175 | assertThat(book.getIsbn()).isEqualTo(updatedBook.getIsbn());
176 | assertThat(book.getAuthor()).isEqualTo(updatedBook.getAuthor());
177 |
178 | }
179 |
180 | @Test
181 | @DisplayName("Deve filtrar livros pelas propriedades")
182 | public void findBookTest(){
183 | //cenario
184 | Book book = createValidBook();
185 |
186 | PageRequest pageRequest = PageRequest.of(0, 10);
187 |
188 | List lista = Arrays.asList(book);
189 | Page page = new PageImpl(lista, pageRequest, 1);
190 | when( repository.findAll(Mockito.any(Example.class), Mockito.any(PageRequest.class)))
191 | .thenReturn(page);
192 |
193 | //execucao
194 | Page result = service.find(book, pageRequest);
195 |
196 |
197 | //verificacoes
198 | assertThat(result.getTotalElements()).isEqualTo(1);
199 | assertThat(result.getContent()).isEqualTo(lista);
200 | assertThat(result.getPageable().getPageNumber()).isEqualTo(0);
201 | assertThat(result.getPageable().getPageSize()).isEqualTo(10);
202 | }
203 |
204 | @Test
205 | @DisplayName("deve obter um livro pelo isbn")
206 | public void getBookByIsbnTest(){
207 | String isbn = "1230";
208 | when(repository.findByIsbn(isbn)).thenReturn( Optional.of(Book.builder().id(1l).isbn(isbn).build()) );
209 |
210 | Optional book = service.getBookByIsbn(isbn);
211 |
212 | assertThat(book.isPresent()).isTrue();
213 | assertThat(book.get().getId()).isEqualTo(1l);
214 | assertThat(book.get().getIsbn()).isEqualTo(isbn);
215 |
216 | verify(repository, times(1)).findByIsbn(isbn);
217 | }
218 | }
219 |
--------------------------------------------------------------------------------
/src/test/java/com/cursodsousa/libraryapi/service/LoanServiceTest.java:
--------------------------------------------------------------------------------
1 | package com.cursodsousa.libraryapi.service;
2 |
3 | import com.cursodsousa.libraryapi.api.dto.LoanFilterDTO;
4 | import com.cursodsousa.libraryapi.exception.BusinessException;
5 | import com.cursodsousa.libraryapi.model.entity.Book;
6 | import com.cursodsousa.libraryapi.model.entity.Loan;
7 | import com.cursodsousa.libraryapi.model.repository.LoanRepository;
8 | import com.cursodsousa.libraryapi.service.impl.LoanServiceImpl;
9 | import org.junit.jupiter.api.BeforeEach;
10 | import org.junit.jupiter.api.DisplayName;
11 | import org.junit.jupiter.api.Test;
12 | import org.junit.jupiter.api.extension.ExtendWith;
13 | import org.mockito.Mockito;
14 | import org.springframework.boot.test.mock.mockito.MockBean;
15 | import org.springframework.data.domain.Example;
16 | import org.springframework.data.domain.Page;
17 | import org.springframework.data.domain.PageImpl;
18 | import org.springframework.data.domain.PageRequest;
19 | import org.springframework.test.context.ActiveProfiles;
20 | import org.springframework.test.context.junit.jupiter.SpringExtension;
21 |
22 | import java.time.LocalDate;
23 | import java.util.Arrays;
24 | import java.util.List;
25 | import java.util.Optional;
26 |
27 | import static org.assertj.core.api.Assertions.assertThat;
28 | import static org.assertj.core.api.Assertions.catchThrowable;
29 | import static org.mockito.Mockito.*;
30 |
31 | @ExtendWith(SpringExtension.class)
32 | @ActiveProfiles("test")
33 | public class LoanServiceTest {
34 |
35 | LoanService service;
36 |
37 | @MockBean
38 | LoanRepository repository;
39 |
40 | @BeforeEach
41 | public void setUp(){
42 | this.service = new LoanServiceImpl(repository);
43 | }
44 |
45 | @Test
46 | @DisplayName("Deve salvar um empréstimo")
47 | public void saveLoanTest(){
48 | Book book = Book.builder().id(1l).build();
49 | String customer = "Fulano";
50 |
51 | Loan savingLoan =
52 | Loan.builder()
53 | .book(book)
54 | .customer(customer)
55 | .loanDate(LocalDate.now())
56 | .build();
57 |
58 | Loan savedLoan = Loan.builder()
59 | .id(1l)
60 | .loanDate(LocalDate.now())
61 | .customer(customer)
62 | .book(book).build();
63 |
64 |
65 | when( repository.existsByBookAndNotReturned(book) ).thenReturn(false);
66 | when( repository.save(savingLoan) ).thenReturn( savedLoan );
67 |
68 | Loan loan = service.save(savingLoan);
69 |
70 | assertThat(loan.getId()).isEqualTo(savedLoan.getId());
71 | assertThat(loan.getBook().getId()).isEqualTo(savedLoan.getBook().getId());
72 | assertThat(loan.getCustomer()).isEqualTo(savedLoan.getCustomer());
73 | assertThat(loan.getLoanDate()).isEqualTo(savedLoan.getLoanDate());
74 | }
75 |
76 | @Test
77 | @DisplayName("Deve lançar erro de negócio ao salvar um empréstimo com livro já emprestado")
78 | public void loanedBookSaveTest(){
79 | Book book = Book.builder().id(1l).build();
80 | String customer = "Fulano";
81 |
82 | Loan savingLoan =
83 | Loan.builder()
84 | .book(book)
85 | .customer(customer)
86 | .loanDate(LocalDate.now())
87 | .build();
88 |
89 | when(repository.existsByBookAndNotReturned(book)).thenReturn(true);
90 |
91 | Throwable exception = catchThrowable(() -> service.save(savingLoan));
92 |
93 | assertThat(exception)
94 | .isInstanceOf(BusinessException.class)
95 | .hasMessage("Book already loaned");
96 |
97 | verify(repository, never()).save(savingLoan);
98 |
99 | }
100 |
101 | @Test
102 | @DisplayName(" Deve obter as informações de um empréstimo pelo ID")
103 | public void getLoanDetaisTest(){
104 | //cenário
105 | Long id = 1l;
106 |
107 | Loan loan = createLoan();
108 | loan.setId(id);
109 |
110 | Mockito.when( repository.findById(id) ).thenReturn(Optional.of(loan));
111 |
112 | //execucao
113 | Optional result = service.getById(id);
114 |
115 | //verificacao
116 | assertThat(result.isPresent()).isTrue();
117 | assertThat(result.get().getId()).isEqualTo(id);
118 | assertThat(result.get().getCustomer()).isEqualTo(loan.getCustomer());
119 | assertThat(result.get().getBook()).isEqualTo(loan.getBook());
120 | assertThat(result.get().getLoanDate()).isEqualTo(loan.getLoanDate());
121 |
122 | verify( repository ).findById(id);
123 |
124 | }
125 |
126 | @Test
127 | @DisplayName("Deve atualizar um empréstimo.")
128 | public void updateLoanTest(){
129 | Loan loan = createLoan();
130 | loan.setId(1l);
131 | loan.setReturned(true);
132 |
133 | when( repository.save(loan) ).thenReturn( loan );
134 |
135 | Loan updatedLoan = service.update(loan);
136 |
137 | assertThat(updatedLoan.getReturned()).isTrue();
138 | verify(repository).save(loan);
139 | }
140 |
141 | @Test
142 | @DisplayName("Deve filtrar empréstimos pelas propriedades")
143 | public void findLoanTest(){
144 | //cenario
145 | LoanFilterDTO loanFilterDTO = LoanFilterDTO.builder().customer("Fulano").isbn("321").build();
146 |
147 | Loan loan = createLoan();
148 | loan.setId(1l);
149 | PageRequest pageRequest = PageRequest.of(0, 10);
150 | List lista = Arrays.asList(loan);
151 |
152 | Page page = new PageImpl(lista, pageRequest, lista.size());
153 | when( repository.findByBookIsbnOrCustomer(
154 | Mockito.anyString(),
155 | Mockito.anyString(),
156 | Mockito.any(PageRequest.class))
157 | )
158 | .thenReturn(page);
159 |
160 | //execucao
161 | Page result = service.find( loanFilterDTO, pageRequest );
162 |
163 |
164 | //verificacoes
165 | assertThat(result.getTotalElements()).isEqualTo(1);
166 | assertThat(result.getContent()).isEqualTo(lista);
167 | assertThat(result.getPageable().getPageNumber()).isEqualTo(0);
168 | assertThat(result.getPageable().getPageSize()).isEqualTo(10);
169 | }
170 |
171 | public static Loan createLoan(){
172 | Book book = Book.builder().id(1l).build();
173 | String customer = "Fulano";
174 |
175 | return Loan.builder()
176 | .book(book)
177 | .customer(customer)
178 | .loanDate(LocalDate.now())
179 | .build();
180 | }
181 | }
182 |
--------------------------------------------------------------------------------