├── .gitignore
├── README.md
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
├── main
├── kotlin
│ └── me
│ │ └── dio
│ │ └── credit
│ │ └── application
│ │ └── system
│ │ ├── CreditApplicationSystemApplication.kt
│ │ ├── configuration
│ │ └── Swagger3Config.kt
│ │ ├── controller
│ │ ├── CreditResource.kt
│ │ └── CustomerResource.kt
│ │ ├── dto
│ │ ├── request
│ │ │ ├── CreditDto.kt
│ │ │ ├── CustomerDto.kt
│ │ │ └── CustomerUpdateDto.kt
│ │ └── response
│ │ │ ├── CreditView.kt
│ │ │ ├── CreditViewList.kt
│ │ │ └── CustomerView.kt
│ │ ├── entity
│ │ ├── Address.kt
│ │ ├── Credit.kt
│ │ └── Customer.kt
│ │ ├── enummeration
│ │ └── Status.kt
│ │ ├── exception
│ │ ├── BusinessException.kt
│ │ ├── ExceptionDetails.kt
│ │ └── RestExceptionHandler.kt
│ │ ├── repository
│ │ ├── CreditRepository.kt
│ │ └── CustomerRepository.kt
│ │ └── service
│ │ ├── ICreditService.kt
│ │ ├── ICustomerService.kt
│ │ └── impl
│ │ ├── CreditService.kt
│ │ └── CustomerService.kt
└── resources
│ ├── application.yml
│ └── db
│ └── migration
│ ├── V1__create_table_customer.sql
│ ├── V2__create_table_credit.sql
│ └── V3__add_table_customer.sql
└── test
├── kotlin
└── me
│ └── dio
│ └── credit
│ └── application
│ └── system
│ ├── CreditApplicationSystemApplicationTests.kt
│ ├── controller
│ └── CustomerResourceTest.kt
│ ├── repository
│ └── CreditRepositoryTest.kt
│ └── service
│ ├── CreditServiceTest.kt
│ └── CustomerServiceTest.kt
└── resources
└── application-test.properties
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | .gradle
3 | build/
4 | !gradle/wrapper/gradle-wrapper.jar
5 | !**/src/main/**/build/
6 | !**/src/test/**/build/
7 |
8 | ### STS ###
9 | .apt_generated
10 | .classpath
11 | .factorypath
12 | .project
13 | .settings
14 | .springBeans
15 | .sts4-cache
16 | bin/
17 | !**/src/main/**/bin/
18 | !**/src/test/**/bin/
19 |
20 | ### IntelliJ IDEA ###
21 | .idea
22 | *.iws
23 | *.iml
24 | *.ipr
25 | out/
26 | !**/src/main/**/out/
27 | !**/src/test/**/out/
28 |
29 | ### NetBeans ###
30 | /nbproject/private/
31 | /nbbuild/
32 | /dist/
33 | /nbdist/
34 | /.nb-gradle/
35 |
36 | ### VS Code ###
37 | .vscode/
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
request-credit-system
2 | API Rest para um Sistema de Analise de Solicitação de Crédito
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | Descrição do Projeto
25 | https://gist.github.com/cami-la/560b455b901778391abd2c9edea81286
26 |
27 |
28 | 
29 | Diagrama UML Simplificado de uma API para Sistema de Avaliação de Crédito
30 |
31 |
32 |
33 | Instrução de Uso
34 | No Terminal/Console:
35 |
36 | - Faça um clone do projeto na sua máquina:
git clone git@github.com:cami-la/credit-application-system.git
37 | - Entre na pasta raiz do projeto:
cd
38 | - Execute o comando:
./gradlew bootrun
39 |
40 | ** Visando facilitar a demostração da aplicação, recomendo instalar apenas o IntelliJ IDEA e executar o projeto através da IDE **
41 |
42 | Autor
43 |
44 |
45 |
46 |
47 | Camila Cavalcante
48 |
49 | Feito com ❤️ por Cami-la 👋🏽 Entre em contato!
50 |
51 | [](https://www.linkedin.com/in/cami-la/)
52 | [](mailto:camiladsantoscavalcante@gmail.com)
53 |
54 | Contribuindo
55 |
56 | Este repositório foi criado para fins de estudo, então contribua com ele.
57 | Se te ajudei de alguma forma, ficarei feliz em saber. Caso você conheça alguém que se identifique com o conteúdo, não
58 | deixe de compatilhar.
59 |
60 | Se possível:
61 |
62 | ⭐️ Star o projeto
63 |
64 | 🐛 Encontrar e relatar issues
65 |
66 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
2 |
3 | plugins {
4 | id 'org.springframework.boot' version '3.0.4'
5 | id 'io.spring.dependency-management' version '1.1.0'
6 | id 'org.jetbrains.kotlin.jvm' version '1.7.22'
7 | id 'org.jetbrains.kotlin.plugin.spring' version '1.7.22'
8 | id 'org.jetbrains.kotlin.plugin.jpa' version '1.7.22'
9 | }
10 |
11 | group = 'me.dio'
12 | version = '0.0.1-SNAPSHOT'
13 | sourceCompatibility = '17'
14 |
15 | repositories {
16 | mavenCentral()
17 | }
18 |
19 | dependencies {
20 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
21 | implementation 'org.springframework.boot:spring-boot-starter-validation'
22 | implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'
23 | implementation 'org.springframework.boot:spring-boot-starter-web'
24 | implementation 'com.fasterxml.jackson.module:jackson-module-kotlin'
25 | implementation 'org.flywaydb:flyway-core'
26 | implementation 'org.jetbrains.kotlin:kotlin-reflect'
27 | runtimeOnly 'com.h2database:h2'
28 | testImplementation 'org.springframework.boot:spring-boot-starter-test'
29 | testImplementation 'io.mockk:mockk:1.13.4'
30 | }
31 |
32 | tasks.withType(KotlinCompile).configureEach {
33 | kotlinOptions {
34 | freeCompilerArgs = ['-Xjsr305=strict']
35 | jvmTarget = '17'
36 | }
37 | }
38 |
39 | tasks.named('test') {
40 | useJUnitPlatform()
41 | }
42 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cami-la/credit-application-system/bb1c688f81bb2db7534d81a5c5b2b2da72b62fc6/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
84 |
85 | APP_NAME="Gradle"
86 | APP_BASE_NAME=${0##*/}
87 |
88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
90 |
91 | # Use the maximum available, or set MAX_FD != -1 to use that value.
92 | MAX_FD=maximum
93 |
94 | warn () {
95 | echo "$*"
96 | } >&2
97 |
98 | die () {
99 | echo
100 | echo "$*"
101 | echo
102 | exit 1
103 | } >&2
104 |
105 | # OS specific support (must be 'true' or 'false').
106 | cygwin=false
107 | msys=false
108 | darwin=false
109 | nonstop=false
110 | case "$( uname )" in #(
111 | CYGWIN* ) cygwin=true ;; #(
112 | Darwin* ) darwin=true ;; #(
113 | MSYS* | MINGW* ) msys=true ;; #(
114 | NONSTOP* ) nonstop=true ;;
115 | esac
116 |
117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
118 |
119 |
120 | # Determine the Java command to use to start the JVM.
121 | if [ -n "$JAVA_HOME" ] ; then
122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
123 | # IBM's JDK on AIX uses strange locations for the executables
124 | JAVACMD=$JAVA_HOME/jre/sh/java
125 | else
126 | JAVACMD=$JAVA_HOME/bin/java
127 | fi
128 | if [ ! -x "$JAVACMD" ] ; then
129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
130 |
131 | Please set the JAVA_HOME variable in your environment to match the
132 | location of your Java installation."
133 | fi
134 | else
135 | JAVACMD=java
136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 |
142 | # Increase the maximum file descriptors if we can.
143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
144 | case $MAX_FD in #(
145 | max*)
146 | MAX_FD=$( ulimit -H -n ) ||
147 | warn "Could not query maximum file descriptor limit"
148 | esac
149 | case $MAX_FD in #(
150 | '' | soft) :;; #(
151 | *)
152 | ulimit -n "$MAX_FD" ||
153 | warn "Could not set maximum file descriptor limit to $MAX_FD"
154 | esac
155 | fi
156 |
157 | # Collect all arguments for the java command, stacking in reverse order:
158 | # * args from the command line
159 | # * the main class name
160 | # * -classpath
161 | # * -D...appname settings
162 | # * --module-path (only if needed)
163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
164 |
165 | # For Cygwin or MSYS, switch paths to Windows format before running java
166 | if "$cygwin" || "$msys" ; then
167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
169 |
170 | JAVACMD=$( cygpath --unix "$JAVACMD" )
171 |
172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
173 | for arg do
174 | if
175 | case $arg in #(
176 | -*) false ;; # don't mess with options #(
177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
178 | [ -e "$t" ] ;; #(
179 | *) false ;;
180 | esac
181 | then
182 | arg=$( cygpath --path --ignore --mixed "$arg" )
183 | fi
184 | # Roll the args list around exactly as many times as the number of
185 | # args, so each arg winds up back in the position where it started, but
186 | # possibly modified.
187 | #
188 | # NB: a `for` loop captures its iteration list before it begins, so
189 | # changing the positional parameters here affects neither the number of
190 | # iterations, nor the values presented in `arg`.
191 | shift # remove old arg
192 | set -- "$@" "$arg" # push replacement arg
193 | done
194 | fi
195 |
196 | # Collect all arguments for the java command;
197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
198 | # shell script including quotes and variable substitutions, so put them in
199 | # double quotes to make sure that they get re-expanded; and
200 | # * put everything else in single quotes, so that it's not re-expanded.
201 |
202 | set -- \
203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
204 | -classpath "$CLASSPATH" \
205 | org.gradle.wrapper.GradleWrapperMain \
206 | "$@"
207 |
208 | # Stop when "xargs" is not available.
209 | if ! command -v xargs >/dev/null 2>&1
210 | then
211 | die "xargs is not available"
212 | fi
213 |
214 | # Use "xargs" to parse quoted args.
215 | #
216 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
217 | #
218 | # In Bash we could simply go:
219 | #
220 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
221 | # set -- "${ARGS[@]}" "$@"
222 | #
223 | # but POSIX shell has neither arrays nor command substitution, so instead we
224 | # post-process each arg (as a line of input to sed) to backslash-escape any
225 | # character that might be a shell metacharacter, then use eval to reverse
226 | # that process (while maintaining the separation between arguments), and wrap
227 | # the whole thing up as a single "set" statement.
228 | #
229 | # This will of course break if any of these variables contains a newline or
230 | # an unmatched quote.
231 | #
232 |
233 | eval "set -- $(
234 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
235 | xargs -n1 |
236 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
237 | tr '\n' ' '
238 | )" '"$@"'
239 |
240 | exec "$JAVACMD" "$@"
241 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if %ERRORLEVEL% equ 0 goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if %ERRORLEVEL% equ 0 goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | set EXIT_CODE=%ERRORLEVEL%
84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
86 | exit /b %EXIT_CODE%
87 |
88 | :mainEnd
89 | if "%OS%"=="Windows_NT" endlocal
90 |
91 | :omega
92 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'credit-application-system'
2 |
--------------------------------------------------------------------------------
/src/main/kotlin/me/dio/credit/application/system/CreditApplicationSystemApplication.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system
2 |
3 | import org.springframework.boot.autoconfigure.SpringBootApplication
4 | import org.springframework.boot.runApplication
5 |
6 | @SpringBootApplication
7 | class CreditApplicationSystemApplication
8 |
9 | fun main(args: Array) {
10 | runApplication(*args)
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/kotlin/me/dio/credit/application/system/configuration/Swagger3Config.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.configuration
2 |
3 | import org.springdoc.core.models.GroupedOpenApi
4 | import org.springframework.context.annotation.Bean
5 | import org.springframework.context.annotation.Configuration
6 |
7 | @Configuration
8 | class Swagger3Config {
9 | @Bean
10 | fun publicApi(): GroupedOpenApi? {
11 | return GroupedOpenApi.builder()
12 | .group("springcreditapplicationsystem-public")
13 | .pathsToMatch("/api/customers/**", "/api/credits/**")
14 | .build()
15 | }
16 | }
--------------------------------------------------------------------------------
/src/main/kotlin/me/dio/credit/application/system/controller/CreditResource.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.controller
2 |
3 | import jakarta.validation.Valid
4 | import me.dio.credit.application.system.dto.request.CreditDto
5 | import me.dio.credit.application.system.dto.response.CreditView
6 | import me.dio.credit.application.system.dto.response.CreditViewList
7 | import me.dio.credit.application.system.entity.Credit
8 | import me.dio.credit.application.system.service.impl.CreditService
9 | import org.springframework.http.HttpStatus
10 | import org.springframework.http.ResponseEntity
11 | import org.springframework.web.bind.annotation.*
12 | import java.util.*
13 | import java.util.stream.Collectors
14 |
15 | @RestController
16 | @RequestMapping("/api/credits")
17 | class CreditResource(
18 | private val creditService: CreditService
19 | ) {
20 |
21 | @PostMapping
22 | fun saveCredit(@RequestBody @Valid creditDto: CreditDto): ResponseEntity {
23 | val credit: Credit = this.creditService.save(creditDto.toEntity())
24 | return ResponseEntity.status(HttpStatus.CREATED)
25 | .body("Credit ${credit.creditCode} - Customer ${credit.customer?.email} saved!")
26 | }
27 |
28 | @GetMapping
29 | fun findAllByCustomerId(@RequestParam(value = "customerId") customerId: Long):
30 | ResponseEntity> {
31 | val creditViewList: List = this.creditService.findAllByCustomer(customerId)
32 | .stream()
33 | .map { credit: Credit -> CreditViewList(credit) }
34 | .collect(Collectors.toList())
35 | return ResponseEntity.status(HttpStatus.OK).body(creditViewList)
36 | }
37 |
38 | @GetMapping("/{creditCode}")
39 | fun findByCreditCode(
40 | @RequestParam(value = "customerId") customerId: Long,
41 | @PathVariable creditCode: UUID
42 | ): ResponseEntity {
43 | val credit: Credit = this.creditService.findByCreditCode(customerId, creditCode)
44 | return ResponseEntity.status(HttpStatus.OK).body(CreditView(credit))
45 | }
46 | }
--------------------------------------------------------------------------------
/src/main/kotlin/me/dio/credit/application/system/controller/CustomerResource.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.controller
2 |
3 | import jakarta.validation.Valid
4 | import me.dio.credit.application.system.dto.request.CustomerDto
5 | import me.dio.credit.application.system.dto.request.CustomerUpdateDto
6 | import me.dio.credit.application.system.dto.response.CustomerView
7 | import me.dio.credit.application.system.entity.Customer
8 | import me.dio.credit.application.system.service.impl.CustomerService
9 | import org.springframework.http.HttpStatus
10 | import org.springframework.http.ResponseEntity
11 | import org.springframework.web.bind.annotation.*
12 |
13 | @RestController
14 | @RequestMapping("/api/customers")
15 | class CustomerResource(
16 | private val customerService: CustomerService
17 | ) {
18 |
19 | @PostMapping
20 | fun saveCustomer(@RequestBody @Valid customerDto: CustomerDto): ResponseEntity {
21 | val savedCustomer: Customer = this.customerService.save(customerDto.toEntity())
22 | return ResponseEntity.status(HttpStatus.CREATED).body(CustomerView(savedCustomer))
23 | }
24 |
25 | @GetMapping("/{id}")
26 | fun findById(@PathVariable id: Long): ResponseEntity {
27 | val customer: Customer = this.customerService.findById(id)
28 | return ResponseEntity.status(HttpStatus.OK).body(CustomerView(customer))
29 | }
30 |
31 | @DeleteMapping("/{id}")
32 | @ResponseStatus(HttpStatus.NO_CONTENT)
33 | fun deleteCustomer(@PathVariable id: Long) = this.customerService.delete(id)
34 |
35 | @PatchMapping
36 | fun upadateCustomer(
37 | @RequestParam(value = "customerId") id: Long,
38 | @RequestBody @Valid customerUpdateDto: CustomerUpdateDto
39 | ): ResponseEntity {
40 | val customer: Customer = this.customerService.findById(id)
41 | val cutomerToUpdate: Customer = customerUpdateDto.toEntity(customer)
42 | val customerUpdated: Customer = this.customerService.save(cutomerToUpdate)
43 | return ResponseEntity.status(HttpStatus.OK).body(CustomerView(customerUpdated))
44 | }
45 | }
--------------------------------------------------------------------------------
/src/main/kotlin/me/dio/credit/application/system/dto/request/CreditDto.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.dto.request
2 |
3 | import jakarta.validation.constraints.Future
4 | import jakarta.validation.constraints.Max
5 | import jakarta.validation.constraints.Min
6 | import jakarta.validation.constraints.NotNull
7 | import me.dio.credit.application.system.entity.Credit
8 | import me.dio.credit.application.system.entity.Customer
9 | import java.math.BigDecimal
10 | import java.time.LocalDate
11 |
12 | data class CreditDto(
13 | @field:NotNull(message = "Invalid input") val creditValue: BigDecimal,
14 | @field:Future val dayFirstOfInstallment: LocalDate,
15 | @field:Min(value = 1) @field:Max(value = 48) val numberOfInstallments: Int,
16 | @field:NotNull(message = "Invalid input") val customerId: Long
17 | ) {
18 |
19 | fun toEntity(): Credit = Credit(
20 | creditValue = this.creditValue,
21 | dayFirstInstallment = this.dayFirstOfInstallment,
22 | numberOfInstallments = this.numberOfInstallments,
23 | customer = Customer(id = this.customerId)
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/kotlin/me/dio/credit/application/system/dto/request/CustomerDto.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.dto.request
2 |
3 | import jakarta.validation.constraints.Email
4 | import jakarta.validation.constraints.NotEmpty
5 | import jakarta.validation.constraints.NotNull
6 | import me.dio.credit.application.system.entity.Address
7 | import me.dio.credit.application.system.entity.Customer
8 | import org.hibernate.validator.constraints.br.CPF
9 | import java.math.BigDecimal
10 |
11 | data class CustomerDto(
12 | @field:NotEmpty(message = "Invalid input") val firstName: String,
13 | @field:NotEmpty(message = "Invalid input") val lastName: String,
14 | @field:NotEmpty(message = "Invalid input")
15 | @field:CPF(message = "This invalid CPF") val cpf: String,
16 | @field:NotNull(message = "Invalid input") val income: BigDecimal,
17 | @field:Email(message = "Invalid email")
18 | @field:NotEmpty(message = "Invalid input") val email: String,
19 | @field:NotEmpty(message = "Invalid input") val password: String,
20 | @field:NotEmpty(message = "Invalid input") val zipCode: String,
21 | @field:NotEmpty(message = "Invalid input") val street: String
22 | ) {
23 |
24 | fun toEntity(): Customer = Customer(
25 | firstName = this.firstName,
26 | lastName = this.lastName,
27 | cpf = this.cpf,
28 | income = this.income,
29 | email = this.email,
30 | password = this.password,
31 | address = Address(
32 | zipCode = this.zipCode,
33 | street = this.street
34 | )
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/kotlin/me/dio/credit/application/system/dto/request/CustomerUpdateDto.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.dto.request
2 |
3 | import jakarta.validation.constraints.NotEmpty
4 | import jakarta.validation.constraints.NotNull
5 | import me.dio.credit.application.system.entity.Customer
6 | import java.math.BigDecimal
7 |
8 | data class CustomerUpdateDto(
9 | @field:NotEmpty(message = "Invalid input") val firstName: String,
10 | @field:NotEmpty(message = "Invalid input") val lastName: String,
11 | @field:NotNull(message = "Invalid input") val income: BigDecimal,
12 | @field:NotEmpty(message = "Invalid input") val zipCode: String,
13 | @field:NotEmpty(message = "Invalid input") val street: String
14 | ) {
15 | fun toEntity(customer: Customer): Customer {
16 | customer.firstName = this.firstName
17 | customer.lastName = this.lastName
18 | customer.income = this.income
19 | customer.address.street = this.street
20 | customer.address.zipCode = this.zipCode
21 | return customer
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/kotlin/me/dio/credit/application/system/dto/response/CreditView.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.dto.response
2 |
3 | import me.dio.credit.application.system.entity.Credit
4 | import me.dio.credit.application.system.enummeration.Status
5 | import java.math.BigDecimal
6 | import java.util.*
7 |
8 | data class CreditView(
9 | val creditCode: UUID,
10 | val creditValue: BigDecimal,
11 | val numberOfInstallment: Int,
12 | val status: Status,
13 | val emailCustomer: String?,
14 | val incomeCustomer: BigDecimal?
15 | ) {
16 | constructor(credit: Credit) : this(
17 | creditCode = credit.creditCode,
18 | creditValue = credit.creditValue,
19 | numberOfInstallment = credit.numberOfInstallments,
20 | status = credit.status,
21 | emailCustomer = credit.customer?.email,
22 | incomeCustomer = credit.customer?.income
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/kotlin/me/dio/credit/application/system/dto/response/CreditViewList.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.dto.response
2 |
3 | import me.dio.credit.application.system.entity.Credit
4 | import java.math.BigDecimal
5 | import java.util.*
6 |
7 | data class CreditViewList(
8 | val creditCode: UUID,
9 | val creditValue: BigDecimal,
10 | val numberOfInstallments: Int
11 | ) {
12 | constructor(credit: Credit) : this(
13 | creditCode = credit.creditCode,
14 | creditValue = credit.creditValue,
15 | numberOfInstallments = credit.numberOfInstallments
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/kotlin/me/dio/credit/application/system/dto/response/CustomerView.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.dto.response
2 |
3 | import me.dio.credit.application.system.entity.Customer
4 | import java.math.BigDecimal
5 |
6 | data class CustomerView(
7 | val firstName: String,
8 | val lastName: String,
9 | val cpf: String,
10 | val income: BigDecimal,
11 | val email: String,
12 | val zipCode: String,
13 | val street: String,
14 | val id: Long?
15 | ) {
16 | constructor(customer: Customer): this (
17 | firstName = customer.firstName,
18 | lastName = customer.lastName,
19 | cpf = customer.cpf,
20 | income = customer.income,
21 | email = customer.email,
22 | zipCode = customer.address.zipCode,
23 | street = customer.address.street,
24 | id = customer.id
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/kotlin/me/dio/credit/application/system/entity/Address.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.entity
2 |
3 | import jakarta.persistence.Column
4 | import jakarta.persistence.Embeddable
5 |
6 | @Embeddable
7 | data class Address(
8 | @Column(nullable = false) var zipCode: String = "",
9 | @Column(nullable = false) var street: String = ""
10 | )
11 |
--------------------------------------------------------------------------------
/src/main/kotlin/me/dio/credit/application/system/entity/Credit.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.entity
2 |
3 | import jakarta.persistence.*
4 | import me.dio.credit.application.system.enummeration.Status
5 | import java.math.BigDecimal
6 | import java.time.LocalDate
7 | import java.util.UUID
8 |
9 | @Entity
10 | //@Table(name = "Credito")
11 | data class Credit (
12 | @Column(nullable = false, unique = true) var creditCode: UUID = UUID.randomUUID(),
13 | @Column(nullable = false) val creditValue: BigDecimal = BigDecimal.ZERO,
14 | @Column(nullable = false) val dayFirstInstallment: LocalDate,
15 | @Column(nullable = false) val numberOfInstallments: Int = 0,
16 | @Enumerated val status: Status = Status.IN_PROGRESS,
17 | @ManyToOne var customer: Customer? = null,
18 | @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long? = null)
19 |
--------------------------------------------------------------------------------
/src/main/kotlin/me/dio/credit/application/system/entity/Customer.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.entity
2 |
3 | import jakarta.persistence.*
4 | import java.math.BigDecimal
5 |
6 | @Entity
7 | //@Table(name = "Cliente")
8 | data class Customer(
9 | @Column(nullable = false) var firstName: String = "",
10 | @Column(nullable = false) var lastName: String = "",
11 | @Column(nullable = false, unique = true) var cpf: String = "",
12 | @Column(nullable = false, unique = true) var email: String = "",
13 | @Column(nullable = false) var income: BigDecimal = BigDecimal.ZERO,
14 | @Column(nullable = false) var password: String = "",
15 | @Column(nullable = false) @Embedded var address: Address = Address(),
16 | @Column(nullable = false) @OneToMany(fetch = FetchType.LAZY,
17 | cascade = arrayOf(CascadeType.REMOVE, CascadeType.PERSIST),
18 | mappedBy = "customer")
19 | var credits: List = mutableListOf(),
20 | @Id @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long? = null
21 | )
22 |
--------------------------------------------------------------------------------
/src/main/kotlin/me/dio/credit/application/system/enummeration/Status.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.enummeration
2 |
3 | enum class Status {
4 | IN_PROGRESS, APPROVED, REJECT
5 | }
6 |
--------------------------------------------------------------------------------
/src/main/kotlin/me/dio/credit/application/system/exception/BusinessException.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.exception
2 |
3 | data class BusinessException(override val message: String?) : RuntimeException(message)
--------------------------------------------------------------------------------
/src/main/kotlin/me/dio/credit/application/system/exception/ExceptionDetails.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.exception
2 |
3 | import java.time.LocalDateTime
4 |
5 | data class ExceptionDetails(
6 | val title: String,
7 | val timestamp: LocalDateTime,
8 | val status: Int,
9 | val exception: String,
10 | val details: MutableMap
11 | )
12 |
--------------------------------------------------------------------------------
/src/main/kotlin/me/dio/credit/application/system/exception/RestExceptionHandler.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.exception
2 |
3 | import org.springframework.dao.DataAccessException
4 | import org.springframework.http.HttpStatus
5 | import org.springframework.http.ResponseEntity
6 | import org.springframework.validation.FieldError
7 | import org.springframework.validation.ObjectError
8 | import org.springframework.web.bind.MethodArgumentNotValidException
9 | import org.springframework.web.bind.annotation.ExceptionHandler
10 | import org.springframework.web.bind.annotation.RestControllerAdvice
11 | import java.time.LocalDateTime
12 |
13 | @RestControllerAdvice
14 | class RestExceptionHandler {
15 | @ExceptionHandler(MethodArgumentNotValidException::class)
16 | fun handlerValidException(ex: MethodArgumentNotValidException): ResponseEntity {
17 | val erros: MutableMap = HashMap()
18 | ex.bindingResult.allErrors.stream().forEach { erro: ObjectError ->
19 | val fieldName: String = (erro as FieldError).field
20 | val messageError: String? = erro.defaultMessage
21 | erros[fieldName] = messageError
22 | }
23 | return ResponseEntity(
24 | ExceptionDetails(
25 | title = "Bad Request! Consult the documentation",
26 | timestamp = LocalDateTime.now(),
27 | status = HttpStatus.BAD_REQUEST.value(),
28 | exception = ex.javaClass.toString(),
29 | details = erros
30 | ), HttpStatus.BAD_REQUEST
31 | )
32 | }
33 |
34 | @ExceptionHandler(DataAccessException::class)
35 | fun handlerValidException(ex: DataAccessException): ResponseEntity {
36 | return ResponseEntity.status(HttpStatus.CONFLICT)
37 | .body(
38 | ExceptionDetails(
39 | title = "Conflict! Consult the documentation",
40 | timestamp = LocalDateTime.now(),
41 | status = HttpStatus.CONFLICT.value(),
42 | exception = ex.javaClass.toString(),
43 | details = mutableMapOf(ex.cause.toString() to ex.message)
44 | )
45 | )
46 | /*return ResponseEntity(
47 | ExceptionDetails(
48 | title = "Bad Request! Consult the documentation",
49 | timestamp = LocalDateTime.now(),
50 | status = HttpStatus.CONFLICT.value(),
51 | exception = ex.javaClass.toString(),
52 | details = mutableMapOf(ex.cause.toString() to ex.message)
53 | ), HttpStatus.CONFLICT
54 | )*/
55 | }
56 |
57 | @ExceptionHandler(BusinessException::class)
58 | fun handlerValidException(ex: BusinessException): ResponseEntity {
59 | return ResponseEntity.status(HttpStatus.BAD_REQUEST)
60 | .body(
61 | ExceptionDetails(
62 | title = "Bad Request! Consult the documentation",
63 | timestamp = LocalDateTime.now(),
64 | status = HttpStatus.BAD_REQUEST.value(),
65 | exception = ex.javaClass.toString(),
66 | details = mutableMapOf(ex.cause.toString() to ex.message)
67 | )
68 | )
69 | }
70 |
71 | @ExceptionHandler(IllegalArgumentException::class)
72 | fun handlerValidException(ex: IllegalArgumentException): ResponseEntity {
73 | return ResponseEntity.status(HttpStatus.BAD_REQUEST)
74 | .body(
75 | ExceptionDetails(
76 | title = "Bad Request! Consult the documentation",
77 | timestamp = LocalDateTime.now(),
78 | status = HttpStatus.BAD_REQUEST.value(),
79 | exception = ex.javaClass.toString(),
80 | details = mutableMapOf(ex.cause.toString() to ex.message)
81 | )
82 | )
83 | }
84 | }
--------------------------------------------------------------------------------
/src/main/kotlin/me/dio/credit/application/system/repository/CreditRepository.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.repository
2 |
3 | import me.dio.credit.application.system.entity.Credit
4 | import org.springframework.data.jpa.repository.JpaRepository
5 | import org.springframework.data.jpa.repository.Query
6 | import org.springframework.stereotype.Repository
7 | import java.util.*
8 |
9 | @Repository
10 | interface CreditRepository: JpaRepository {
11 | fun findByCreditCode(creditCode: UUID) : Credit?
12 |
13 | @Query(value = "SELECT * FROM CREDIT WHERE CUSTOMER_ID = ?1", nativeQuery = true)
14 | fun findAllByCustomerId(customerId: Long): List
15 | }
--------------------------------------------------------------------------------
/src/main/kotlin/me/dio/credit/application/system/repository/CustomerRepository.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.repository
2 |
3 | import me.dio.credit.application.system.entity.Customer
4 | import org.springframework.data.jpa.repository.JpaRepository
5 | import org.springframework.stereotype.Repository
6 |
7 | @Repository
8 | interface CustomerRepository: JpaRepository
--------------------------------------------------------------------------------
/src/main/kotlin/me/dio/credit/application/system/service/ICreditService.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.service
2 |
3 | import me.dio.credit.application.system.entity.Credit
4 | import java.util.UUID
5 |
6 | interface ICreditService {
7 | fun save(credit: Credit): Credit
8 | fun findAllByCustomer(customerId: Long): List
9 | fun findByCreditCode(customerId: Long, creditCode: UUID): Credit
10 | }
--------------------------------------------------------------------------------
/src/main/kotlin/me/dio/credit/application/system/service/ICustomerService.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.service
2 |
3 | import me.dio.credit.application.system.entity.Customer
4 |
5 | interface ICustomerService {
6 | fun save(customer: Customer): Customer
7 | fun findById(id: Long): Customer
8 | fun delete(id: Long)
9 | }
--------------------------------------------------------------------------------
/src/main/kotlin/me/dio/credit/application/system/service/impl/CreditService.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.service.impl
2 |
3 | import me.dio.credit.application.system.entity.Credit
4 | import me.dio.credit.application.system.exception.BusinessException
5 | import me.dio.credit.application.system.repository.CreditRepository
6 | import me.dio.credit.application.system.service.ICreditService
7 | import org.springframework.stereotype.Service
8 | import java.lang.IllegalArgumentException
9 | import java.time.LocalDate
10 | import java.util.*
11 |
12 | @Service
13 | class CreditService(
14 | private val creditRepository: CreditRepository,
15 | private val customerService: CustomerService
16 | ) : ICreditService {
17 | override fun save(credit: Credit): Credit {
18 | this.validDayFirstInstallment(credit.dayFirstInstallment)
19 | credit.apply {
20 | customer = customerService.findById(credit.customer?.id!!)
21 | }
22 | return this.creditRepository.save(credit)
23 | }
24 |
25 | override fun findAllByCustomer(customerId: Long): List =
26 | this.creditRepository.findAllByCustomerId(customerId)
27 |
28 | override fun findByCreditCode(customerId: Long, creditCode: UUID): Credit {
29 | val credit: Credit = (this.creditRepository.findByCreditCode(creditCode)
30 | ?: throw BusinessException("Creditcode $creditCode not found"))
31 | return if (credit.customer?.id == customerId) credit
32 | else throw IllegalArgumentException("Contact admin")
33 | /*if (credit.customer?.id == customerId) {
34 | return credit
35 | } else {
36 | throw RuntimeException("Contact admin")
37 | }*/
38 | }
39 |
40 | private fun validDayFirstInstallment(dayFirstInstallment: LocalDate): Boolean {
41 | return if (dayFirstInstallment.isBefore(LocalDate.now().plusMonths(3))) true
42 | else throw BusinessException("Invalid Date")
43 | }
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/src/main/kotlin/me/dio/credit/application/system/service/impl/CustomerService.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.service.impl
2 |
3 | import me.dio.credit.application.system.entity.Customer
4 | import me.dio.credit.application.system.exception.BusinessException
5 | import me.dio.credit.application.system.repository.CustomerRepository
6 | import me.dio.credit.application.system.service.ICustomerService
7 | import org.springframework.stereotype.Service
8 |
9 | @Service
10 | class CustomerService(
11 | private val customerRepository: CustomerRepository
12 | ): ICustomerService {
13 | override fun save(customer: Customer): Customer = this.customerRepository.save(customer)
14 |
15 | override fun findById(id: Long): Customer = this.customerRepository.findById(id)
16 | .orElseThrow{throw BusinessException("Id $id not found") }
17 |
18 | override fun delete(id: Long) {
19 | val customer: Customer = this.findById(id)
20 | this.customerRepository.delete(customer)
21 | }
22 | }
--------------------------------------------------------------------------------
/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | datasource:
3 | url: jdbc:h2:mem:credit_application_system_db
4 | username: cami
5 | password:
6 | jpa:
7 | show-sql: true
8 | #hibernate:
9 | properties:
10 | hibernate.format_sql: true
11 | h2:
12 | console:
13 | enabled: true
14 | path: /h2-console
15 | settings:
16 | trace: false
17 | web-allow-others: false
18 | springdoc:
19 | swagger-ui:
20 | path: /swagger-ui.html
--------------------------------------------------------------------------------
/src/main/resources/db/migration/V1__create_table_customer.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE customer (
2 | id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
3 | first_name VARCHAR(255) NOT NULL,
4 | last_name VARCHAR(255) NOT NULL,
5 | cpf VARCHAR(255) NOT NULL,
6 | email VARCHAR(255) NOT NULL,
7 | password VARCHAR(255) NOT NULL,
8 | zip_code VARCHAR(255) NOT NULL,
9 | street VARCHAR(255) NOT NULL,
10 | CONSTRAINT pk_customer PRIMARY KEY (id)
11 | );
12 |
13 | ALTER TABLE customer ADD CONSTRAINT uc_customer_cpf UNIQUE (cpf);
14 |
15 | ALTER TABLE customer ADD CONSTRAINT uc_customer_email UNIQUE (email);
--------------------------------------------------------------------------------
/src/main/resources/db/migration/V2__create_table_credit.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE credit (
2 | id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
3 | credit_code UUID NOT NULL,
4 | credit_value DECIMAL NOT NULL,
5 | day_first_installment date NOT NULL,
6 | number_of_installments INT NOT NULL,
7 | status INT,
8 | customer_id BIGINT,
9 | CONSTRAINT pk_credit PRIMARY KEY (id)
10 | );
11 |
12 | ALTER TABLE credit ADD CONSTRAINT uc_credit_creditcode UNIQUE (credit_code);
13 |
14 | ALTER TABLE credit ADD CONSTRAINT FK_CREDIT_ON_CUSTOMER FOREIGN KEY (customer_id) REFERENCES customer (id);
--------------------------------------------------------------------------------
/src/main/resources/db/migration/V3__add_table_customer.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE customer ADD income DECIMAL NOT NULL
--------------------------------------------------------------------------------
/src/test/kotlin/me/dio/credit/application/system/CreditApplicationSystemApplicationTests.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system
2 |
3 | import org.junit.jupiter.api.Test
4 | import org.springframework.boot.test.context.SpringBootTest
5 |
6 | @SpringBootTest
7 | class CreditApplicationSystemApplicationTests {
8 |
9 | @Test
10 | fun contextLoads() {
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/test/kotlin/me/dio/credit/application/system/controller/CustomerResourceTest.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.controller
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper
4 | import me.dio.credit.application.system.dto.request.CustomerDto
5 | import me.dio.credit.application.system.dto.request.CustomerUpdateDto
6 | import me.dio.credit.application.system.entity.Customer
7 | import me.dio.credit.application.system.repository.CustomerRepository
8 | import org.junit.jupiter.api.AfterEach
9 | import org.junit.jupiter.api.BeforeEach
10 | import org.junit.jupiter.api.Test
11 | import org.springframework.beans.factory.annotation.Autowired
12 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
13 | import org.springframework.boot.test.context.SpringBootTest
14 | import org.springframework.http.MediaType
15 | import org.springframework.test.context.ActiveProfiles
16 | import org.springframework.test.context.ContextConfiguration
17 | import org.springframework.test.web.servlet.MockMvc
18 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
19 | import org.springframework.test.web.servlet.result.MockMvcResultHandlers
20 | import org.springframework.test.web.servlet.result.MockMvcResultMatchers
21 | import java.math.BigDecimal
22 | import java.util.Random
23 |
24 | @SpringBootTest
25 | @ActiveProfiles("test")
26 | @AutoConfigureMockMvc
27 | @ContextConfiguration
28 | class CustomerResourceTest {
29 | @Autowired
30 | private lateinit var customerRepository: CustomerRepository
31 |
32 | @Autowired
33 | private lateinit var mockMvc: MockMvc
34 |
35 | @Autowired
36 | private lateinit var objectMapper: ObjectMapper
37 |
38 | companion object {
39 | const val URL: String = "/api/customers"
40 | }
41 |
42 | @BeforeEach
43 | fun setup() = customerRepository.deleteAll()
44 |
45 | @AfterEach
46 | fun tearDown() = customerRepository.deleteAll()
47 |
48 | @Test
49 | fun `should create a customer and return 201 status`() {
50 | //given
51 | val customerDto: CustomerDto = builderCustomerDto()
52 | val valueAsString: String = objectMapper.writeValueAsString(customerDto)
53 | //when
54 | //then
55 | mockMvc.perform(
56 | MockMvcRequestBuilders.post(URL)
57 | .contentType(MediaType.APPLICATION_JSON)
58 | .content(valueAsString)
59 | )
60 | .andExpect(MockMvcResultMatchers.status().isCreated)
61 | .andExpect(MockMvcResultMatchers.jsonPath("$.firstName").value("Cami"))
62 | .andExpect(MockMvcResultMatchers.jsonPath("$.lastName").value("Cavalcante"))
63 | .andExpect(MockMvcResultMatchers.jsonPath("$.cpf").value("28475934625"))
64 | .andExpect(MockMvcResultMatchers.jsonPath("$.email").value("camila@email.com"))
65 | .andExpect(MockMvcResultMatchers.jsonPath("$.income").value("1000.0"))
66 | .andExpect(MockMvcResultMatchers.jsonPath("$.zipCode").value("000000"))
67 | .andExpect(MockMvcResultMatchers.jsonPath("$.street").value("Rua da Cami, 123"))
68 | .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1))
69 | .andDo(MockMvcResultHandlers.print())
70 | }
71 |
72 | @Test
73 | fun `should not save a customer with same CPF and return 409 status`() {
74 | //given
75 | customerRepository.save(builderCustomerDto().toEntity())
76 | val customerDto: CustomerDto = builderCustomerDto()
77 | val valueAsString: String = objectMapper.writeValueAsString(customerDto)
78 | //when
79 | //then
80 | mockMvc.perform(
81 | MockMvcRequestBuilders.post(URL)
82 | .contentType(MediaType.APPLICATION_JSON)
83 | .content(valueAsString)
84 | )
85 | .andExpect(MockMvcResultMatchers.status().isConflict)
86 | .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Conflict! Consult the documentation"))
87 | .andExpect(MockMvcResultMatchers.jsonPath("$.timestamp").exists())
88 | .andExpect(MockMvcResultMatchers.jsonPath("$.status").value(409))
89 | .andExpect(
90 | MockMvcResultMatchers.jsonPath("$.exception")
91 | .value("class org.springframework.dao.DataIntegrityViolationException")
92 | )
93 | .andExpect(MockMvcResultMatchers.jsonPath("$.details[*]").isNotEmpty)
94 | .andDo(MockMvcResultHandlers.print())
95 | }
96 |
97 | @Test
98 | fun `should not save a customer with empty firstName and return 400 status`() {
99 | //given
100 | val customerDto: CustomerDto = builderCustomerDto(firstName = "")
101 | val valueAsString: String = objectMapper.writeValueAsString(customerDto)
102 | //when
103 | //then
104 | mockMvc.perform(
105 | MockMvcRequestBuilders.post(URL)
106 | .content(valueAsString)
107 | .contentType(MediaType.APPLICATION_JSON)
108 | )
109 | .andExpect(MockMvcResultMatchers.status().isBadRequest)
110 | .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Bad Request! Consult the documentation"))
111 | .andExpect(MockMvcResultMatchers.jsonPath("$.timestamp").exists())
112 | .andExpect(MockMvcResultMatchers.jsonPath("$.status").value(400))
113 | .andExpect(
114 | MockMvcResultMatchers.jsonPath("$.exception")
115 | .value("class org.springframework.web.bind.MethodArgumentNotValidException")
116 | )
117 | .andExpect(MockMvcResultMatchers.jsonPath("$.details[*]").isNotEmpty)
118 | .andDo(MockMvcResultHandlers.print())
119 | }
120 |
121 | @Test
122 | fun `should find customer by id and return 200 status`() {
123 | //given
124 | val customer: Customer = customerRepository.save(builderCustomerDto().toEntity())
125 | //when
126 | //then
127 | mockMvc.perform(
128 | MockMvcRequestBuilders.get("$URL/${customer.id}")
129 | .accept(MediaType.APPLICATION_JSON)
130 | )
131 | .andExpect(MockMvcResultMatchers.status().isOk)
132 | .andExpect(MockMvcResultMatchers.jsonPath("$.firstName").value("Cami"))
133 | .andExpect(MockMvcResultMatchers.jsonPath("$.lastName").value("Cavalcante"))
134 | .andExpect(MockMvcResultMatchers.jsonPath("$.cpf").value("28475934625"))
135 | .andExpect(MockMvcResultMatchers.jsonPath("$.email").value("camila@email.com"))
136 | .andExpect(MockMvcResultMatchers.jsonPath("$.income").value("1000.0"))
137 | .andExpect(MockMvcResultMatchers.jsonPath("$.zipCode").value("000000"))
138 | .andExpect(MockMvcResultMatchers.jsonPath("$.street").value("Rua da Cami, 123"))
139 | //.andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1))
140 | .andDo(MockMvcResultHandlers.print())
141 | }
142 |
143 | @Test
144 | fun `should not find customer with invalid id and return 400 status`() {
145 | //given
146 | val invalidId: Long = 2L
147 | //when
148 | //then
149 | mockMvc.perform(
150 | MockMvcRequestBuilders.get("$URL/$invalidId")
151 | .accept(MediaType.APPLICATION_JSON)
152 | )
153 | .andExpect(MockMvcResultMatchers.status().isBadRequest)
154 | .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Bad Request! Consult the documentation"))
155 | .andExpect(MockMvcResultMatchers.jsonPath("$.timestamp").exists())
156 | .andExpect(MockMvcResultMatchers.jsonPath("$.status").value(400))
157 | .andExpect(
158 | MockMvcResultMatchers.jsonPath("$.exception")
159 | .value("class me.dio.credit.application.system.exception.BusinessException")
160 | )
161 | .andExpect(MockMvcResultMatchers.jsonPath("$.details[*]").isNotEmpty)
162 | .andDo(MockMvcResultHandlers.print())
163 | }
164 |
165 | @Test
166 | fun `should delete customer by id and return 204 status`() {
167 | //given
168 | val customer: Customer = customerRepository.save(builderCustomerDto().toEntity())
169 | //when
170 | //then
171 | mockMvc.perform(
172 | MockMvcRequestBuilders.delete("$URL/${customer.id}")
173 | .accept(MediaType.APPLICATION_JSON)
174 | )
175 | .andExpect(MockMvcResultMatchers.status().isNoContent)
176 | .andDo(MockMvcResultHandlers.print())
177 | }
178 |
179 | @Test
180 | fun `should not delete customer by id and return 400 status`() {
181 | //given
182 | val invalidId: Long = Random().nextLong()
183 | //when
184 | //then
185 | mockMvc.perform(
186 | MockMvcRequestBuilders.delete("$URL/${invalidId}")
187 | .accept(MediaType.APPLICATION_JSON)
188 | )
189 | .andExpect(MockMvcResultMatchers.status().isBadRequest)
190 | .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Bad Request! Consult the documentation"))
191 | .andExpect(MockMvcResultMatchers.jsonPath("$.timestamp").exists())
192 | .andExpect(MockMvcResultMatchers.jsonPath("$.status").value(400))
193 | .andExpect(
194 | MockMvcResultMatchers.jsonPath("$.exception")
195 | .value("class me.dio.credit.application.system.exception.BusinessException")
196 | )
197 | .andExpect(MockMvcResultMatchers.jsonPath("$.details[*]").isNotEmpty)
198 | .andDo(MockMvcResultHandlers.print())
199 | }
200 |
201 | @Test
202 | fun `should update a customer and return 200 status`() {
203 | //given
204 | val customer: Customer = customerRepository.save(builderCustomerDto().toEntity())
205 | val customerUpdateDto: CustomerUpdateDto = builderCustomerUpdateDto()
206 | val valueAsString: String = objectMapper.writeValueAsString(customerUpdateDto)
207 | //when
208 | //then
209 | mockMvc.perform(
210 | MockMvcRequestBuilders.patch("$URL?customerId=${customer.id}")
211 | .contentType(MediaType.APPLICATION_JSON)
212 | .content(valueAsString)
213 | )
214 | .andExpect(MockMvcResultMatchers.status().isOk)
215 | .andExpect(MockMvcResultMatchers.jsonPath("$.firstName").value("CamiUpdate"))
216 | .andExpect(MockMvcResultMatchers.jsonPath("$.lastName").value("CavalcanteUpdate"))
217 | .andExpect(MockMvcResultMatchers.jsonPath("$.cpf").value("28475934625"))
218 | .andExpect(MockMvcResultMatchers.jsonPath("$.email").value("camila@email.com"))
219 | .andExpect(MockMvcResultMatchers.jsonPath("$.income").value("5000.0"))
220 | .andExpect(MockMvcResultMatchers.jsonPath("$.zipCode").value("45656"))
221 | .andExpect(MockMvcResultMatchers.jsonPath("$.street").value("Rua Updated"))
222 | //.andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1))
223 | .andDo(MockMvcResultHandlers.print())
224 | }
225 |
226 | @Test
227 | fun `should not update a customer with invalid id and return 400 status`() {
228 | //given
229 | val invalidId: Long = Random().nextLong()
230 | val customerUpdateDto: CustomerUpdateDto = builderCustomerUpdateDto()
231 | val valueAsString: String = objectMapper.writeValueAsString(customerUpdateDto)
232 | //when
233 | //then
234 | mockMvc.perform(
235 | MockMvcRequestBuilders.patch("$URL?customerId=$invalidId")
236 | .contentType(MediaType.APPLICATION_JSON)
237 | .content(valueAsString)
238 | )
239 | .andExpect(MockMvcResultMatchers.status().isBadRequest)
240 | .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Bad Request! Consult the documentation"))
241 | .andExpect(MockMvcResultMatchers.jsonPath("$.timestamp").exists())
242 | .andExpect(MockMvcResultMatchers.jsonPath("$.status").value(400))
243 | .andExpect(
244 | MockMvcResultMatchers.jsonPath("$.exception")
245 | .value("class me.dio.credit.application.system.exception.BusinessException")
246 | )
247 | .andExpect(MockMvcResultMatchers.jsonPath("$.details[*]").isNotEmpty)
248 | .andDo(MockMvcResultHandlers.print())
249 | }
250 |
251 |
252 | private fun builderCustomerDto(
253 | firstName: String = "Cami",
254 | lastName: String = "Cavalcante",
255 | cpf: String = "28475934625",
256 | email: String = "camila@email.com",
257 | income: BigDecimal = BigDecimal.valueOf(1000.0),
258 | password: String = "1234",
259 | zipCode: String = "000000",
260 | street: String = "Rua da Cami, 123",
261 | ) = CustomerDto(
262 | firstName = firstName,
263 | lastName = lastName,
264 | cpf = cpf,
265 | email = email,
266 | income = income,
267 | password = password,
268 | zipCode = zipCode,
269 | street = street
270 | )
271 |
272 | private fun builderCustomerUpdateDto(
273 | firstName: String = "CamiUpdate",
274 | lastName: String = "CavalcanteUpdate",
275 | income: BigDecimal = BigDecimal.valueOf(5000.0),
276 | zipCode: String = "45656",
277 | street: String = "Rua Updated"
278 | ): CustomerUpdateDto = CustomerUpdateDto(
279 | firstName = firstName,
280 | lastName = lastName,
281 | income = income,
282 | zipCode = zipCode,
283 | street = street
284 | )
285 | }
--------------------------------------------------------------------------------
/src/test/kotlin/me/dio/credit/application/system/repository/CreditRepositoryTest.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.repository
2 |
3 | import me.dio.credit.application.system.entity.Address
4 | import me.dio.credit.application.system.entity.Credit
5 | import me.dio.credit.application.system.entity.Customer
6 | import org.assertj.core.api.Assertions
7 | import org.junit.jupiter.api.BeforeEach
8 | import org.junit.jupiter.api.Test
9 | import org.springframework.beans.factory.annotation.Autowired
10 | import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
11 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
12 | import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
13 | import org.springframework.test.context.ActiveProfiles
14 | import java.math.BigDecimal
15 | import java.time.LocalDate
16 | import java.time.Month
17 | import java.util.*
18 |
19 | @ActiveProfiles("test")
20 | @DataJpaTest
21 | @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
22 | class CreditRepositoryTest {
23 | @Autowired lateinit var creditRepository: CreditRepository
24 | @Autowired lateinit var testEntityManager: TestEntityManager
25 |
26 | private lateinit var customer: Customer
27 | private lateinit var credit1: Credit
28 | private lateinit var credit2: Credit
29 |
30 | @BeforeEach fun setup () {
31 | customer = testEntityManager.persist(buildCustomer())
32 | credit1 = testEntityManager.persist(buildCredit(customer = customer))
33 | credit2 = testEntityManager.persist(buildCredit(customer = customer))
34 | }
35 |
36 | @Test
37 | fun `should find credit by credit code`() {
38 | //given
39 | val creditCode1 = UUID.fromString("aa547c0f-9a6a-451f-8c89-afddce916a29")
40 | val creditCode2 = UUID.fromString("49f740be-46a7-449b-84e7-ff5b7986d7ef")
41 | credit1.creditCode = creditCode1
42 | credit2.creditCode = creditCode2
43 | //when
44 | val fakeCredit1: Credit = creditRepository.findByCreditCode(creditCode1)!!
45 | val fakeCredit2: Credit = creditRepository.findByCreditCode(creditCode2)!!
46 | //then
47 | Assertions.assertThat(fakeCredit1).isNotNull
48 | Assertions.assertThat(fakeCredit2).isNotNull
49 | Assertions.assertThat(fakeCredit1).isSameAs(credit1)
50 | Assertions.assertThat(fakeCredit2).isSameAs(credit2)
51 | }
52 |
53 | @Test
54 | fun `should find all credits by customer id`() {
55 | //given
56 | val customerId: Long = 1L
57 | //when
58 | val creditList: List = creditRepository.findAllByCustomerId(customerId)
59 | //then
60 | Assertions.assertThat(creditList).isNotEmpty
61 | Assertions.assertThat(creditList.size).isEqualTo(2)
62 | Assertions.assertThat(creditList).contains(credit1, credit2)
63 | }
64 |
65 | private fun buildCredit(
66 | creditValue: BigDecimal = BigDecimal.valueOf(500.0),
67 | dayFirstInstallment: LocalDate = LocalDate.of(2023, Month.APRIL, 22),
68 | numberOfInstallments: Int = 5,
69 | customer: Customer
70 | ): Credit = Credit(
71 | creditValue = creditValue,
72 | dayFirstInstallment = dayFirstInstallment,
73 | numberOfInstallments = numberOfInstallments,
74 | customer = customer
75 | )
76 | private fun buildCustomer(
77 | firstName: String = "Cami",
78 | lastName: String = "Cavalcante",
79 | cpf: String = "28475934625",
80 | email: String = "camila@gmail.com",
81 | password: String = "12345",
82 | zipCode: String = "12345",
83 | street: String = "Rua da Cami",
84 | income: BigDecimal = BigDecimal.valueOf(1000.0),
85 | ) = Customer(
86 | firstName = firstName,
87 | lastName = lastName,
88 | cpf = cpf,
89 | email = email,
90 | password = password,
91 | address = Address(
92 | zipCode = zipCode,
93 | street = street,
94 | ),
95 | income = income,
96 | )
97 |
98 | }
--------------------------------------------------------------------------------
/src/test/kotlin/me/dio/credit/application/system/service/CreditServiceTest.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.service
2 |
3 | import io.mockk.*
4 | import io.mockk.impl.annotations.InjectMockKs
5 | import io.mockk.impl.annotations.MockK
6 | import io.mockk.junit5.MockKExtension
7 | import me.dio.credit.application.system.entity.Credit
8 | import me.dio.credit.application.system.entity.Customer
9 | import me.dio.credit.application.system.exception.BusinessException
10 | import me.dio.credit.application.system.repository.CreditRepository
11 | import me.dio.credit.application.system.service.impl.CreditService
12 | import me.dio.credit.application.system.service.impl.CustomerService
13 | import org.assertj.core.api.Assertions
14 | import org.junit.jupiter.api.AfterEach
15 | import org.junit.jupiter.api.BeforeEach
16 | import org.junit.jupiter.api.Test
17 | import org.junit.jupiter.api.extension.ExtendWith
18 | import java.math.BigDecimal
19 | import java.time.LocalDate
20 | import java.util.*
21 |
22 | @ExtendWith(MockKExtension::class)
23 | class CreditServiceTest {
24 | @MockK
25 | lateinit var creditRepository: CreditRepository
26 |
27 | @MockK
28 | lateinit var customerService: CustomerService
29 |
30 | @InjectMockKs
31 | lateinit var creditService: CreditService
32 |
33 | @BeforeEach
34 | fun setUp() {
35 | MockKAnnotations.init(this)
36 | //creditService = CreditService(creditRepository, customerService)
37 | }
38 |
39 | @AfterEach
40 | fun tearDown() {
41 | unmockkAll()
42 | }
43 |
44 | @Test
45 | fun `should create credit `() {
46 | //given
47 | val credit: Credit = buildCredit()
48 | val customerId: Long = 1L
49 |
50 | every { customerService.findById(customerId) } returns credit.customer!!
51 | every { creditRepository.save(credit) } returns credit
52 | //when
53 | val actual: Credit = this.creditService.save(credit)
54 | //then
55 | verify(exactly = 1) { customerService.findById(customerId) }
56 | verify(exactly = 1) { creditRepository.save(credit) }
57 |
58 | Assertions.assertThat(actual).isNotNull
59 | Assertions.assertThat(actual).isSameAs(credit)
60 | }
61 |
62 | @Test
63 | fun `should not create credit when invalid day first installment`() {
64 | //given
65 | val invalidDayFirstInstallment: LocalDate = LocalDate.now().plusMonths(5)
66 | val credit: Credit = buildCredit(dayFirstInstallment = invalidDayFirstInstallment)
67 |
68 | every { creditRepository.save(credit) } answers { credit }
69 | //when
70 | Assertions.assertThatThrownBy { creditService.save(credit) }
71 | .isInstanceOf(BusinessException::class.java)
72 | .hasMessage("Invalid Date")
73 | //then
74 | verify(exactly = 0) { creditRepository.save(any()) }
75 | }
76 |
77 | @Test
78 | fun `should return list of credits for a customer`() {
79 | //given
80 | val customerId: Long = 1L
81 | val expectedCredits: List = listOf(buildCredit(), buildCredit(), buildCredit())
82 |
83 | every { creditRepository.findAllByCustomerId(customerId) } returns expectedCredits
84 | //when
85 | val actual: List = creditService.findAllByCustomer(customerId)
86 | //then
87 | Assertions.assertThat(actual).isNotNull
88 | Assertions.assertThat(actual).isNotEmpty
89 | Assertions.assertThat(actual).isSameAs(expectedCredits)
90 |
91 | verify(exactly = 1) { creditRepository.findAllByCustomerId(customerId) }
92 | }
93 |
94 | @Test
95 | fun `should return credit for a valid customer and credit code`() {
96 | //given
97 | val customerId: Long = 1L
98 | val creditCode: UUID = UUID.randomUUID()
99 | val credit: Credit = buildCredit(customer = Customer(id = customerId))
100 |
101 | every { creditRepository.findByCreditCode(creditCode) } returns credit
102 | //when
103 | val actual: Credit = creditService.findByCreditCode(customerId, creditCode)
104 | //then
105 | Assertions.assertThat(actual).isNotNull
106 | Assertions.assertThat(actual).isSameAs(credit)
107 |
108 | verify(exactly = 1) { creditRepository.findByCreditCode(creditCode) }
109 | }
110 |
111 | @Test
112 | fun `should throw BusinessException for invalid credit code`() {
113 | //given
114 | val customerId: Long = 1L
115 | val invalidCreditCode: UUID = UUID.randomUUID()
116 |
117 | every { creditRepository.findByCreditCode(invalidCreditCode) } returns null
118 | //when
119 | //then
120 | Assertions.assertThatThrownBy { creditService.findByCreditCode(customerId, invalidCreditCode) }
121 | .isInstanceOf(BusinessException::class.java)
122 | .hasMessage("Creditcode $invalidCreditCode not found")
123 | //then
124 | verify(exactly = 1) { creditRepository.findByCreditCode(invalidCreditCode) }
125 | }
126 |
127 | @Test
128 | fun `should throw IllegalArgumentException for different customer ID`() {
129 | //given
130 | val customerId: Long = 1L
131 | val creditCode: UUID = UUID.randomUUID()
132 | val credit: Credit = buildCredit(customer = Customer(id = 2L))
133 |
134 | every { creditRepository.findByCreditCode(creditCode) } returns credit
135 | //when
136 | //then
137 | Assertions.assertThatThrownBy { creditService.findByCreditCode(customerId, creditCode) }
138 | .isInstanceOf(IllegalArgumentException::class.java)
139 | .hasMessage("Contact admin")
140 |
141 | verify { creditRepository.findByCreditCode(creditCode) }
142 | }
143 |
144 | companion object {
145 | private fun buildCredit(
146 | creditValue: BigDecimal = BigDecimal.valueOf(100.0),
147 | dayFirstInstallment: LocalDate = LocalDate.now().plusMonths(2L),
148 | numberOfInstallments: Int = 15,
149 | customer: Customer = CustomerServiceTest.buildCustomer()
150 | ): Credit = Credit(
151 | creditValue = creditValue,
152 | dayFirstInstallment = dayFirstInstallment,
153 | numberOfInstallments = numberOfInstallments,
154 | customer = customer
155 | )
156 | }
157 | }
--------------------------------------------------------------------------------
/src/test/kotlin/me/dio/credit/application/system/service/CustomerServiceTest.kt:
--------------------------------------------------------------------------------
1 | package me.dio.credit.application.system.service
2 |
3 | import io.mockk.every
4 | import io.mockk.impl.annotations.InjectMockKs
5 | import io.mockk.impl.annotations.MockK
6 | import io.mockk.junit5.MockKExtension
7 | import io.mockk.just
8 | import io.mockk.runs
9 | import io.mockk.verify
10 | import me.dio.credit.application.system.entity.Address
11 | import me.dio.credit.application.system.entity.Customer
12 | import me.dio.credit.application.system.exception.BusinessException
13 | import me.dio.credit.application.system.repository.CustomerRepository
14 | import me.dio.credit.application.system.service.impl.CustomerService
15 | import org.assertj.core.api.Assertions
16 | import org.junit.jupiter.api.Test
17 | import org.junit.jupiter.api.extension.ExtendWith
18 | import org.springframework.test.context.ActiveProfiles
19 | import java.math.BigDecimal
20 | import java.util.*
21 |
22 | //@ActiveProfiles("test")
23 | @ExtendWith(MockKExtension::class)
24 | class CustomerServiceTest {
25 | @MockK lateinit var customerRepository: CustomerRepository
26 | @InjectMockKs lateinit var customerService: CustomerService
27 |
28 | @Test
29 | fun `should create customer`(){
30 | //given
31 | val fakeCustomer: Customer = buildCustomer()
32 | every { customerRepository.save(any()) } returns fakeCustomer
33 | //when
34 | val actual: Customer = customerService.save(fakeCustomer)
35 | //then
36 | Assertions.assertThat(actual).isNotNull
37 | Assertions.assertThat(actual).isSameAs(fakeCustomer)
38 | verify(exactly = 1) { customerRepository.save(fakeCustomer) }
39 | }
40 |
41 | @Test
42 | fun `should find customer by id`() {
43 | //given
44 | val fakeId: Long = Random().nextLong()
45 | val fakeCustomer: Customer = buildCustomer(id = fakeId)
46 | every { customerRepository.findById(fakeId) } returns Optional.of(fakeCustomer)
47 | //when
48 | val actual: Customer = customerService.findById(fakeId)
49 | //then
50 | Assertions.assertThat(actual).isNotNull
51 | Assertions.assertThat(actual).isExactlyInstanceOf(Customer::class.java)
52 | Assertions.assertThat(actual).isSameAs(fakeCustomer)
53 | verify(exactly = 1) { customerRepository.findById(fakeId) }
54 | }
55 |
56 | @Test
57 | fun `should not find customer by invalid id and throw BusinessException`() {
58 | //given
59 | val fakeId: Long = Random().nextLong()
60 | every { customerRepository.findById(fakeId) } returns Optional.empty()
61 | //when
62 | //then
63 | Assertions.assertThatExceptionOfType(BusinessException::class.java)
64 | .isThrownBy { customerService.findById(fakeId) }
65 | .withMessage("Id $fakeId not found")
66 | verify(exactly = 1) { customerRepository.findById(fakeId) }
67 | }
68 |
69 | @Test
70 | fun `should delete customer by id`() {
71 | //given
72 | val fakeId: Long = Random().nextLong()
73 | val fakeCustomer: Customer = buildCustomer(id = fakeId)
74 | every { customerRepository.findById(fakeId) } returns Optional.of(fakeCustomer)
75 | every { customerRepository.delete(fakeCustomer) } just runs
76 | //when
77 | customerService.delete(fakeId)
78 | //then
79 | verify(exactly = 1) { customerRepository.findById(fakeId) }
80 | verify(exactly = 1) { customerRepository.delete(fakeCustomer) }
81 | }
82 |
83 |
84 | companion object {
85 | fun buildCustomer(
86 | firstName: String = "Cami",
87 | lastName: String = "Cavalcante",
88 | cpf: String = "28475934625",
89 | email: String = "camila@gmail.com",
90 | password: String = "12345",
91 | zipCode: String = "12345",
92 | street: String = "Rua da Cami",
93 | income: BigDecimal = BigDecimal.valueOf(1000.0),
94 | id: Long = 1L
95 | ) = Customer(
96 | firstName = firstName,
97 | lastName = lastName,
98 | cpf = cpf,
99 | email = email,
100 | password = password,
101 | address = Address(
102 | zipCode = zipCode,
103 | street = street,
104 | ),
105 | income = income,
106 | id = id
107 | )
108 | }
109 | }
--------------------------------------------------------------------------------
/src/test/resources/application-test.properties:
--------------------------------------------------------------------------------
1 | spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1
2 | spring.datasource.username=sa
3 | spring.datasource.password=
4 | spring.datasource.driver-class-name=org.h2.Driver
5 | spring.datasource.initialization-mode=always
6 |
7 | spring.jpa.show-sql=true
8 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
9 | spring.jpa.properties.hibernate.format_sql=true
10 |
11 | spring.flyway.enabled=false
--------------------------------------------------------------------------------