Gitlab - Argos ALM by PALO IT

presale repository has been created

parents
# Created by https://www.toptal.com/developers/gitignore/api/intellij,visualstudiocode,eclipse,netbeans,maven,gradle
# Edit at https://www.toptal.com/developers/gitignore?templates=intellij,visualstudiocode,eclipse,netbeans,maven,gradle
### Eclipse ###
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
.recommenders
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# PyDev specific (Python IDE for Eclipse)
*.pydevproject
# CDT-specific (C/C++ Development Tooling)
.cproject
# CDT- autotools
.autotools
# Java annotation processor (APT)
.factorypath
# PDT-specific (PHP Development Tools)
.buildpath
# sbteclipse plugin
.target
# Tern plugin
.tern-project
# TeXlipse plugin
.texlipse
# STS (Spring Tool Suite)
.springBeans
# Code Recommenders
.recommenders/
# Annotation Processing
.apt_generated/
.apt_generated_test/
# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet
# Uncomment this line if you wish to ignore the project description file.
# Typically, this file would be tracked if it contains build/dependency configurations:
#.project
### Eclipse Patch ###
# Spring Boot Tooling
.sts4-cache/
### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
.idea/
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
.idea/artifacts
.idea/compiler.xml
.idea/jarRepositories.xml
.idea/modules.xml
.idea/*.iml
.idea/modules
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Intellij Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
*.iml
modules.xml
.idea/misc.xml
*.ipr
# Sonarlint plugin
# https://plugins.jetbrains.com/plugin/7973-sonarlint
.idea/**/sonarlint/
# SonarQube Plugin
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
.idea/**/sonarIssues.xml
# Markdown Navigator plugin
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
.idea/**/markdown-navigator.xml
.idea/**/markdown-navigator-enh.xml
.idea/**/markdown-navigator/
# Cache file creation bug
# See https://youtrack.jetbrains.com/issue/JBR-2257
.idea/$CACHE_FILE$
# CodeStream plugin
# https://plugins.jetbrains.com/plugin/12206-codestream
.idea/codestream.xml
# Azure Toolkit for IntelliJ plugin
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
.idea/**/azureSettings.xml
### Maven ###
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
.mvn/wrapper/maven-wrapper.jar
# Eclipse m2e generated files
# Eclipse Core
.project
# JDT-specific (Eclipse Java Development Tools)
.classpath
### NetBeans ###
**/nbproject/private/
**/nbproject/Makefile-*.mk
**/nbproject/Package-*.bash
build/
nbbuild/
dist/
nbdist/
.nb-gradle/
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
### Gradle ###
.gradle
**/build/
!src/**/build/
# Ignore Gradle GUI config
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Avoid ignore Gradle wrappper properties
!gradle-wrapper.properties
# Cache of project
.gradletasknamecache
# Eclipse Gradle plugin generated files
# Eclipse Core
# JDT-specific (Eclipse Java Development Tools)
### Gradle Patch ###
# Java heap dump
*.hprof
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
# End of https://www.toptal.com/developers/gitignore/api/intellij+all,eclipse,netbeans,visualstudiocode,sublimetext
# End of https://www.toptal.com/developers/gitignore/api/intellij,visualstudiocode,eclipse,netbeans,maven,gradle
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.3/apache-maven-3.9.3-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
FROM maven:3.9.3-eclipse-temurin-17 as build
WORKDIR /workspace/app
COPY . ./
RUN mvn clean install -DskipTests
FROM amazoncorretto:17-alpine
COPY --from=build /workspace/app/presales-ms-server/target/*-exec.jar ./app.jar
CMD ["java","-jar", "app.jar"]
\ No newline at end of file
# Properties
| Name | Description | Default Value |
|-----------------------|---------------------------------------|---------------|
| clients.rest.exchanges.presales.urls.create | Url of the rest service to create presales | |
| clients.rest.exchanges.presales.urls.get | Url of the rest service to get presales | |
\ No newline at end of file
# Presales Ms
Presales Ms is responsible to << CHANGE ME write here the motivation and responsibility to create this project >>
This project was build based on PaloIT templates.
## Requirements
### Software
* Java JDK >=17
* NodeJS (to use httpyac cli)
### IDE
* Set up code formatting template (root folder intellij-java-google-style.xml)
* Change properties encoding to UTF-8
* Follow the PaloIT setup guide https://paloit.atlassian.net/wiki/spaces/PAPA/pages/2152661057/IDE+Setup
### CLI
* HTTP Yac.
````bash
npm install -g httpyac
````
### MacOS M1
Is required to set up the next env variables to be able to use testcontainers and spring docker
```bash
export DOCKER_HOST="unix://$HOME/.colima/docker.sock"
export TESTCONTAINERS_RYUK_DISABLED=true
```
## Usage
The application has the next Spring Profiles that configure the application behavior
| Profile | Behavior |
|----------|------------------------------------|
| local | Active typical one line log format |
- Compile
```bash
mvn clean install
```
- Run Application
```bash
mvn spring-boot:run -pl presales-ms-server -Dspring-boot.run.profiles=local
```
- Run Integration Tests
```bash
mvn failsafe:integration-test
```
- Build Image
```bash
mvn clean install
mvn spring-boot:build-image -pl presales-ms-server
```
- Test API's and/or GRPC Services
```bash
httpyac send http-collection/ --all
```
## Development Conduct Guide
- Make sure before any commit your code use the formatting template `intellij-java-google-style.xml`
- Document your `@ConfigurationProperties` in code and [Properties Markdown](PROPERTIES.md) you can follow the Spring Guide
[ConfigurationProperties Documentation](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#appendix.configuration-metadata.annotation-processor.automatic-metadata-generation)
this will let external people know what configurations are available.
- Document in this readme new **Spring profiles**
### About **liquibase**
- Scripts file name have start with `db.` and include a timestamp to order the files the time stamp have to follow the pattern `%Y%m%d%H%M`. You can use the next unix script to generate the name
```bash
date +db.%Y%m%d%H%M
```
- Scripts have to include attributes
- `id` with a unique identifier suggested ULID. This is an [Online ULID](https://ulidgenerator.com/)
- `author` with the email of the author
- `comment` with a short description of the change
- `tagDatabase` to be able to do rollback during production deployment
\ No newline at end of file
This project was created using the [archetect](https://github.com/archetect/archetect) tool with the template `https://gitlab.palo-it.net/hive-tech/architecture/archetect/customer-templates/cardif-brasil/arch-back-java-archetype-spring-boot-service-grpc`.
The next answers were typed
project: `presales`
suffix: `ms`
group-prefix: `br.com.cardif`
service-type: `rest` of [grpc, rest, all]
persistence: `MongoDB` of [PostgreSQL, MySql, H2, MongoDB]
redis: `none` of [none, repository, cache, both]
version: "3"
services:
mongo:
image: mongo
ports:
- 27017:27017
environment:
MONGO_INITDB_ROOT_USERNAME: user
MONGO_INITDB_ROOT_PASSWORD: pa55word
\ No newline at end of file
@host=http://localhost:8080
### Actuator Health
GET /actuator/health
?? status == 200
### Actuator Info
GET /actuator/info
?? status == 200
### Actuator Metrics
GET /actuator/metrics
?? status == 200
### Actuator Prometheus
GET /actuator/prometheus
?? status == 200
\ No newline at end of file
@host=http://localhost:8080
# @name create
POST /presales
Content-Type: application/json
{
"name":"test_name"
}
?? status == 201
###
# @name findById
# @ref create
GET /presales/{{ create.id }}
?? status == 200
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.2.0
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %WRAPPER_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
SET WRAPPER_SHA_256_SUM=""
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
)
IF NOT %WRAPPER_SHA_256_SUM%=="" (
powershell -Command "&{"^
"$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
"If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
" Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
" Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
" Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
" exit 1;"^
"}"^
"}"
if ERRORLEVEL 1 goto error
)
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% ^
%JVM_CONFIG_MAVEN_PROPS% ^
%MAVEN_OPTS% ^
%MAVEN_DEBUG_OPTS% ^
-classpath %WRAPPER_JAR% ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%"=="on" pause
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
cmd /C exit /B %ERROR_CODE%
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.2</version>
</parent>
<groupId>br.com.cardif.presales-ms</groupId>
<artifactId>presales-ms-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>presales-ms::parent</name>
<packaging>pom</packaging>
<modules>
<module>presales-ms-persistence</module>
<module>presales-ms-core</module>
<module>presales-ms-clients</module>
<module>presales-ms-server</module>
<module>presales-ms-integration-test</module>
</modules>
<properties>
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<!-- Dependencies -->
<spring-cloud.version>2022.0.3</spring-cloud.version>
<org.mapstruct.version>1.5.3.Final</org.mapstruct.version>
<io.vavr.version>0.10.4</io.vavr.version>
<logstash.logback.encoder.version>7.4</logstash.logback.encoder.version>
<!-- Plugins -->
<org.jacoco.version>0.8.10</org.jacoco.version>
<!-- Override Because Vulnerability -->
<snakeyaml.version>2.0</snakeyaml.version>
<h2.version>2.2.220</h2.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
<version>${io.vavr.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<release>${java.version}</release>
<source>${java.version}</source>
<target>${java.version}</target>
<parameters>true</parameters>
<testCompilerArgument>-parameters</testCompilerArgument>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs><arg>-Amapstruct.defaultComponentModel=spring</arg></compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${org.jacoco.version}</version>
<executions>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>br.com.cardif.presales-ms</groupId>
<artifactId>presales-ms-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>presales-ms-clients</artifactId>
<properties>
<spring-boot.repackage.skip>true</spring-boot.repackage.skip>
</properties>
<dependencies>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>presales-ms-core</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
</dependencies>
</project>
package br.com.cardif.presales.client.rest;
import static io.vavr.API.$;
import static io.vavr.API.Case;
import static io.vavr.Predicates.instanceOf;
import java.util.Collections;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import br.com.cardif.presales.client.rest.mapper.PresalesRestMapper;
import br.com.cardif.presales.client.rest.properties.RestProperties;
import br.com.cardif.presales.client.rest.request.CreatePresalesRequest;
import br.com.cardif.presales.client.rest.response.PresalesResponse;
import br.com.cardif.presales.core.client.PresalesClient;
import br.com.cardif.presales.core.domain.PresalesDO;
import br.com.cardif.presales.core.exceptions.ClientIntegrationException;
import br.com.cardif.presales.core.exceptions.ErrorCodes;
import io.vavr.control.Try;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
public class PresalesRestClient implements PresalesClient {
private final RestTemplate restTemplate;
private final RestProperties.ExchangeProperties exchangeProperties;
private final PresalesRestMapper presalesRestMapper;
private static final String EXCHANGE_KEY = "presales";
private static final String EXCHANGE_CREATE_KEY = "create";
private static final String EXCHANGE_GET_KEY = "get";
private static final String EXCHANGE_GET_ID_PATH = "id";
public PresalesRestClient(RestTemplateBuilder builder, RestProperties restProperties,
PresalesRestMapper presalesRestMapper) {
this.restTemplate = builder.build();
this.exchangeProperties = restProperties.getExchanges().get(EXCHANGE_KEY);
this.presalesRestMapper = presalesRestMapper;
}
@Override
public String createPresales(PresalesDO presales) throws ClientIntegrationException {
return Try.of(() -> {
CreatePresalesRequest request = presalesRestMapper.fromPresalesDOToCreatePresalesRequest(presales);
String url = exchangeProperties.getUrls().get(EXCHANGE_CREATE_KEY);
return restTemplate.postForObject(url, request, PresalesResponse.class);
})
.map(PresalesResponse::getId)
.mapFailure(
Case($(instanceOf(RestClientException.class) ), ex -> {
log.error("Error calling createCat service code:[{}] message:[{}] ",ex.getMessage(),ex.getMessage());
return new ClientIntegrationException(ex, ErrorCodes.ERROR_CLIENT_CREATING_CAT.getCode());
})
)
.get();
}
@Override
public PresalesDO getPresales(String id) throws ClientIntegrationException {
return Try.of(() -> {
String url = exchangeProperties.getUrls().get(EXCHANGE_GET_KEY);
return restTemplate.getForObject(url, PresalesResponse.class, Collections.singletonMap(EXCHANGE_GET_ID_PATH, id));
})
.map(presalesRestMapper::fromPresalesResponseToPresalesDO)
.mapFailure(
Case($(instanceOf(RestClientException.class) ), ex -> {
log.error("Error calling createCat service code:[{}] message:[{}] ",ex.getMessage(),ex.getMessage());
return new ClientIntegrationException(ex, ErrorCodes.ERROR_CLIENT_GETTING_CAT.getCode());
})
)
.get();
}
}
package br.com.cardif.presales.client.rest.config;
import br.com.cardif.presales.client.rest.properties.HttpClientProperties;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
@Configuration
public class RestTemplateConfiguration {
@Bean
RestTemplateCustomizer okHttpRestTemplateCustomizer(HttpClientProperties config) {
ConnectionPool okHttpConnectionPool = new ConnectionPool(
config.getMaxIdle(),
config.getMaxKeepAlive(),
config.getMaxKeepAliveTimeUnit());
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectionPool(okHttpConnectionPool)
.connectTimeout(config.getConnectionTimeout())
.readTimeout(config.getReadTimeout())
.callTimeout(config.getCallTimeout())
.retryOnConnectionFailure(false);
return restTemplate -> restTemplate.setRequestFactory(
new OkHttp3ClientHttpRequestFactory(builder.build()));
}
}
\ No newline at end of file
package br.com.cardif.presales.client.rest.constants;
public class ClientMetricsConstants {
private ClientMetricsConstants(){};
}
package br.com.cardif.presales.client.rest.mapper;
import org.mapstruct.Mapper;
import br.com.cardif.presales.client.rest.request.CreatePresalesRequest;
import br.com.cardif.presales.client.rest.response.PresalesResponse;
import br.com.cardif.presales.core.domain.PresalesDO;
@Mapper
public interface PresalesRestMapper {
CreatePresalesRequest fromPresalesDOToCreatePresalesRequest(PresalesDO presalesDO);
PresalesDO fromPresalesResponseToPresalesDO(PresalesResponse presalesResponse);
}
package br.com.cardif.presales.client.rest.properties;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.context.annotation.Configuration;
@Getter
@Setter
@Configuration
@ConfigurationProperties("clients.rest.pool")
public class HttpClientProperties {
private static final int HTTP_MAX_IDLE = 5;
private static final long HTTP_KEEP_ALIVE = 5L;
private static final int HTTP_CONNECTION_TIMEOUT = 10;
private static final int HTTP_READ_TIMEOUT = 60;
private int maxIdle = HTTP_MAX_IDLE;
private long maxKeepAlive = HTTP_KEEP_ALIVE;
private TimeUnit maxKeepAliveTimeUnit = TimeUnit.MINUTES;
@DurationUnit(ChronoUnit.SECONDS)
private Duration connectionTimeout = Duration.ofSeconds(HTTP_CONNECTION_TIMEOUT);
@DurationUnit(ChronoUnit.SECONDS)
private Duration readTimeout = Duration.ofSeconds(HTTP_READ_TIMEOUT);
@DurationUnit(ChronoUnit.SECONDS)
private Duration callTimeout = Duration.ofSeconds(HTTP_READ_TIMEOUT);
}
\ No newline at end of file
package br.com.cardif.presales.client.rest.properties;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import lombok.Getter;
@Getter
@Component
@ConfigurationProperties("clients.rest")
public class RestProperties {
private Map<String,ExchangeProperties> exchanges = new HashMap();
@Getter
public static class ExchangeProperties{
private Map<String,String> urls = new HashMap();
}
}
\ No newline at end of file
package br.com.cardif.presales.client.rest.request;
import lombok.Data;
@Data
public class CreatePresalesRequest {
private String name;
}
\ No newline at end of file
package br.com.cardif.presales.client.rest.response;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class PresalesResponse {
private String id;
private String name;
}
package br.com.cardif.presales.client.rest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowableOfType;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.response.MockRestResponseCreators.*;
import java.util.UUID;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestClientException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import br.com.cardif.presales.client.rest.mapper.PresalesRestMapper;
import br.com.cardif.presales.client.rest.properties.RestProperties;
import br.com.cardif.presales.client.rest.response.PresalesResponse;
import br.com.cardif.presales.core.domain.PresalesDO;
import br.com.cardif.presales.core.exceptions.ClientIntegrationException;
import br.com.cardif.presales.core.exceptions.ErrorCodes;
@RestClientTest(components = {
PresalesRestClient.class, PresalesRestMapper.class
})
@EnableConfigurationProperties(RestProperties.class)
@ExtendWith(MockitoExtension.class)
class PresalesRestClientTest {
@Autowired
private PresalesRestClient presalesRestClient;
@Autowired
private MockRestServiceServer server;
@Autowired
private ObjectMapper objectMapper;
@Test
void createPresales__successCreation__returnId() throws JsonProcessingException {
// given
PresalesDO presales = PresalesDO.builder().name("fixed_name").build();
PresalesResponse response = new PresalesResponse(presales.getName(), UUID.randomUUID().toString());
server.expect(method(HttpMethod.POST)).andRespond(
withSuccess(objectMapper.writeValueAsString(response), MediaType.APPLICATION_JSON));
// when
String result = presalesRestClient.createPresales(presales);
// then
assertThat(result).isEqualTo(response.getId());
}
@Test
void createPresales__errorCreation__throwClientIntegrationException() {
// given
PresalesDO any = PresalesDO.builder().name("fixed_name").build();
server.expect(method(HttpMethod.POST)).andRespond(withBadRequest());
// when
ClientIntegrationException exception = catchThrowableOfType(() -> presalesRestClient.createPresales(any),
ClientIntegrationException.class);
// then
assertThat(exception).hasCauseInstanceOf(RestClientException.class);
assertThat(exception.getCode()).isEqualTo(ErrorCodes.ERROR_CLIENT_CREATING_CAT.getCode());
}
@Test
void getPresales__presalesFound__returnPresales() throws JsonProcessingException {
// given
String id = "123";
PresalesResponse response = new PresalesResponse("name", UUID.randomUUID().toString());
server.expect(method(HttpMethod.GET)).andRespond(
withSuccess(objectMapper.writeValueAsString(response), MediaType.APPLICATION_JSON));
// when
PresalesDO result = presalesRestClient.getPresales(id);
// then
assertThat(result).isNotNull().satisfies(presalesDO -> {
assertThat(presalesDO.getId()).isEqualTo(response.getId());
assertThat(presalesDO.getName()).isEqualTo(response.getName());
});
}
@Test
void getPresales__presalesNotFound__throwClientIntegrationException() {
// given
String id = "567";
server.expect(method(HttpMethod.GET)).andRespond(withResourceNotFound());
// when
ClientIntegrationException exception = catchThrowableOfType(() -> presalesRestClient.getPresales(id),
ClientIntegrationException.class);
// then
assertThat(exception).hasCauseInstanceOf(RestClientException.class);
assertThat(exception.getCode()).isEqualTo(ErrorCodes.ERROR_CLIENT_GETTING_CAT.getCode());
}
}
package br.com.cardif.presales.client.rest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
package br.com.cardif.presales.client.rest.mapper;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullSource;
import org.mapstruct.factory.Mappers;
import br.com.cardif.presales.client.rest.request.CreatePresalesRequest;
import br.com.cardif.presales.client.rest.response.PresalesResponse;
import br.com.cardif.presales.core.domain.PresalesDO;
class PresalesRestMapperTest {
private PresalesRestMapper mapper;
@BeforeEach
void init() {
mapper = Mappers.getMapper(PresalesRestMapper.class);
}
@NullSource
@ParameterizedTest
void fromPresalesDOToCreatePresalesRequest__nullInput__nullOutput(PresalesDO presalesDO) {
CreatePresalesRequest result = mapper.fromPresalesDOToCreatePresalesRequest(presalesDO);
assertThat(result).isNull();
}
@Test
void fromPresalesDOToCreatePresalesRequest__anyInput__allFieldsAreEquals() {
PresalesDO presalesDO = PresalesDO.builder().name("name_" + UUID.randomUUID().toString())
.id("id_" + UUID.randomUUID().toString()).build();
CreatePresalesRequest result = mapper.fromPresalesDOToCreatePresalesRequest(presalesDO);
assertThat(result).isNotNull().usingRecursiveComparison().isEqualTo(presalesDO);
}
@NullSource
@ParameterizedTest
void fromPresalesResponseToPresalesDO__nullInput__nullOutput(PresalesResponse presalesResponse) {
PresalesDO result = mapper.fromPresalesResponseToPresalesDO(presalesResponse);
assertThat(result).isNull();
}
@Test
void fromPresalesResponseToPresalesDO__notNullInput__allFieldsAreEquals() {
PresalesResponse presalesResponse = new PresalesResponse("id_" + UUID.randomUUID().toString(),
"name_" + UUID.randomUUID().toString());
PresalesDO result = mapper.fromPresalesResponseToPresalesDO(presalesResponse);
assertThat(result).isNotNull()
.usingRecursiveComparison()
.comparingOnlyFields("id","name")
.isEqualTo(presalesResponse);
}
}
clients.rest.exchanges.presales.urls.create=http://presales:8080/
clients.rest.exchanges.presales.urls.get=http://presales:8080/{id}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>br.com.cardif.presales-ms</groupId>
<artifactId>presales-ms-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>presales-ms-core</artifactId>
<properties>
<spring-boot.repackage.skip>true</spring-boot.repackage.skip>
</properties>
<dependencies>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>presales-ms-persistence</artifactId>
<version>${project.parent.version}</version>
</dependency>
</dependencies>
</project>
package br.com.cardif.presales.core.client;
import br.com.cardif.presales.core.domain.PresalesDO;
import br.com.cardif.presales.core.exceptions.ClientIntegrationException;
public interface PresalesClient {
String createPresales(PresalesDO presales) throws ClientIntegrationException;
PresalesDO getPresales(String id) throws ClientIntegrationException;
}
package br.com.cardif.presales.core.domain;
import java.io.Serializable;
import java.time.Instant;
import br.com.cardif.presales.core.enums.PresalesStatus;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PresalesDO implements Serializable {
private String id;
private String name;
private PresalesStatus status;
private Instant createdDatetime;
private Instant lastModifiedDatetime;
}
\ No newline at end of file
package br.com.cardif.presales.core.enums;
public enum PresalesStatus {
ACTIVE,DELETED,SUSPENDED;
}
\ No newline at end of file
package br.com.cardif.presales.core.exceptions;
import lombok.Getter;
@Getter
public class BusinessErrorException extends RuntimeException {
public static final String BUSINESS_ERROR_MESSAGE = "business.error";
String code;
String[] args;
public BusinessErrorException() {
super(BUSINESS_ERROR_MESSAGE);
this.code = BUSINESS_ERROR_MESSAGE;
}
public BusinessErrorException(Throwable cause) {
super(BUSINESS_ERROR_MESSAGE, cause);
this.code = BUSINESS_ERROR_MESSAGE;
}
public BusinessErrorException(String code, String... args) {
super(code);
this.code = code;
this.args = args;
}
public BusinessErrorException(Throwable cause, String code, String... args) {
super(code, cause);
this.code = code;
this.args = args;
}
}
package br.com.cardif.presales.core.exceptions;
import lombok.Getter;
/**
* This exception will be thrown by integration clients and must be handled by
*/
@Getter
public class ClientIntegrationException extends RuntimeException {
String code;
String[] args;
public ClientIntegrationException(String code, String... args) {
super(code);
this.code = code;
this.args = args;
}
public ClientIntegrationException(Throwable cause, String code, String... args) {
super(code, cause);
this.code = code;
this.args = args;
}
}
\ No newline at end of file
package br.com.cardif.presales.core.exceptions;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* Use this class to to have custom codes, this codes must match with
* codes in messages.properties. You can pass
*/
@Getter
@AllArgsConstructor
public enum ErrorCodes {
// Eg. You can pass this constant as parameter to the ResourceNotFoundException
// PRESALES_NOT_FOUND("1000")
ERROR_CLIENT_CREATING_CAT("1001"),
ERROR_CLIENT_GETTING_CAT("1002");
private String code;
}
package br.com.cardif.presales.core.exceptions;
import lombok.Getter;
@Getter
public class ResourceNotFoundException extends RuntimeException {
public static final String RESOURCE_NOT_FOUND_MESSAGE = "resource.not.found.exception";
String code;
String[] args;
public ResourceNotFoundException() {
super(RESOURCE_NOT_FOUND_MESSAGE);
this.code = RESOURCE_NOT_FOUND_MESSAGE;
}
public ResourceNotFoundException(Throwable cause) {
super(RESOURCE_NOT_FOUND_MESSAGE, cause);
this.code = RESOURCE_NOT_FOUND_MESSAGE;
}
public ResourceNotFoundException(String code, String... args) {
super(code);
this.code = code;
this.args = args;
}
public ResourceNotFoundException(Throwable cause, String code, String... args) {
super(code, cause);
this.code = code;
this.args = args;
}
}
package br.com.cardif.presales.core.mappers;
import br.com.cardif.presales.core.domain.PresalesDO;
import br.com.cardif.presales.persistence.mongo.documents.PresalesDocument;
import org.mapstruct.Mapper;
@Mapper
public interface PresalesMapper {
PresalesDocument domainToPersistenceObject(PresalesDO presales);
PresalesDO persistenceObjectToDomain(PresalesDocument presalesDocument);
}
\ No newline at end of file
package br.com.cardif.presales.core.service;
import br.com.cardif.presales.core.domain.PresalesDO;
public interface PresalesService {
PresalesDO createPresales(PresalesDO presales);
PresalesDO findById(String id);
void deleteById(String id);
}
package br.com.cardif.presales.core.service.impl;
import java.time.Instant;
import br.com.cardif.presales.core.domain.PresalesDO;
import br.com.cardif.presales.core.enums.PresalesStatus;
import br.com.cardif.presales.core.exceptions.ResourceNotFoundException;
import br.com.cardif.presales.core.mappers.PresalesMapper;
import br.com.cardif.presales.core.service.PresalesService;
import br.com.cardif.presales.persistence.mongo.documents.PresalesDocument;
import br.com.cardif.presales.persistence.mongo.repositories.PresalesMongoRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class PresalesServiceImpl implements PresalesService {
private final PresalesMongoRepository repository;
private final PresalesMapper mapper;
@Override
public PresalesDO createPresales(PresalesDO presales) {
PresalesDocument entity = mapper.domainToPersistenceObject(presales);
entity.setStatus(PresalesStatus.ACTIVE.toString());
entity.setCreatedDatetime(Instant.now());
entity.setLastModifiedDatetime(Instant.now());
entity = repository.save(entity);
return mapper.persistenceObjectToDomain(entity);
}
@Override
public PresalesDO findById(String id) {
return repository.findById(id)
.map(mapper::persistenceObjectToDomain)
.orElseThrow(ResourceNotFoundException::new);
}
@Override
public void deleteById(String id){
repository.updateStatusById(PresalesStatus.DELETED.toString(),id);
}
}
package br.com.cardif.presales.core.service.impl;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mapstruct.factory.Mappers;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import br.com.cardif.presales.core.domain.PresalesDO;
import br.com.cardif.presales.core.exceptions.ResourceNotFoundException;
import br.com.cardif.presales.core.mappers.PresalesMapper;
import br.com.cardif.presales.core.service.PresalesService;
import br.com.cardif.presales.persistence.mongo.documents.PresalesDocument;
import br.com.cardif.presales.persistence.mongo.repositories.PresalesMongoRepository;
@ExtendWith(MockitoExtension.class)
class PresalesServiceImplTest {
private PresalesService service;
private PresalesMapper mapper;
@Mock
private PresalesMongoRepository repository;
@BeforeEach
void init() {
service = new PresalesServiceImpl(repository, Mappers.getMapper(PresalesMapper.class));
}
@Test
void createPresales__compareId__pass() {
String id = "-";
PresalesDocument entity = new PresalesDocument();
entity.setId(id);
when(repository.save(any())).thenReturn(entity);
var response = service.createPresales(PresalesDO.builder().build());
assertEquals(id, response.getId());
verify(repository).save(any());
}
@Test
void findById__idNotFound__throwException() {
String in = "";
when(repository.findById(any())).thenReturn(Optional.empty());
assertThrows(ResourceNotFoundException.class, () -> service.findById(in));
verify(repository).findById(any());
}
@Test
void deleteById__nullInput__pass() {
String in = null;
when(repository.updateStatusById(any(), eq(in))).thenReturn(0L);
service.deleteById(in);
verify(repository).updateStatusById(any(), any());
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>br.com.cardif.presales-ms</groupId>
<artifactId>presales-ms-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>presales-ms-integration-test</artifactId>
<dependencies>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>presales-ms-server</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mongodb</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<skipIfEmpty>true</skipIfEmpty>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
package br.com.cardif.presales.server;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationFeature;
import br.com.cardif.presales.server.controller.request.CreatePresalesRequest;
import br.com.cardif.presales.server.controller.response.PresalesResponse;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import static org.assertj.core.api.Assertions.assertThat;
@Testcontainers
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PresalesRestIntegrationTest {
@Container
@ServiceConnection
static MongoDBContainer mongoDBContainer = new MongoDBContainer();
private static final String URL_TEST_API = "http://localhost:";
private static final String PATH_CONTEXT = "/presales";
private static final String PATH_CREATE = "";
private static final String PATH_GET_BY_ID = "/{id}";
@Value(value="${local.server.port}")
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Test
void createPresalesAndFindIt__allFieldsSent__responseNameEqualsToRequestName() throws Exception {
var createPresalesRequest = new CreatePresalesRequest();
createPresalesRequest.setName("integration_test");
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, false);
ObjectWriter writer = mapper.writer().withDefaultPrettyPrinter();
String requestJson = writer.writeValueAsString(createPresalesRequest);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
var request = new HttpEntity<>(requestJson, headers);
PresalesResponse respCreate = this.restTemplate.postForObject(URL_TEST_API+port+PATH_CONTEXT+PATH_CREATE, request, PresalesResponse.class);
assertThat(respCreate.getName()).isEqualTo(createPresalesRequest.getName());
PresalesResponse respFindById = this.restTemplate.getForObject(URL_TEST_API+port+PATH_CONTEXT+PATH_GET_BY_ID, PresalesResponse.class, respCreate.getId());
assertThat(respFindById).isNotNull();
assertThat(respFindById.getName()).isEqualTo(respCreate.getName());
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>br.com.cardif.presales-ms</groupId>
<artifactId>presales-ms-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>presales-ms-persistence</artifactId>
<properties>
<spring-boot.repackage.skip>true</spring-boot.repackage.skip>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
</dependencies>
</project>
package br.com.cardif.presales.persistence.mongo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
@Configuration
@EnableMongoRepositories(basePackages = "br.com.cardif.presales.persistence.mongo")
public class MongoConfiguration {
}
package br.com.cardif.presales.persistence.mongo.documents;
import java.time.Instant;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Document("presales")
public class PresalesDocument {
@Id
private String id;
private String name;
private String status;
private Instant lastModifiedDatetime;
private Instant createdDatetime;
}
package br.com.cardif.presales.persistence.mongo.repositories;
import br.com.cardif.presales.persistence.mongo.documents.PresalesDocument;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.mongodb.repository.Update;
public interface PresalesMongoRepository extends MongoRepository<PresalesDocument, String> {
@Query("{ 'id' : ?0 }")
@Update("{ '$set' : { 'status': ?0 } }")
long updateStatusById(String status, String id);
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>br.com.cardif.presales-ms</groupId>
<artifactId>presales-ms-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>presales-ms-server</artifactId>
<dependencies>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>presales-ms-core</artifactId>
<version>${project.parent.version}</version>
</dependency>
<!-- Production Ready -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-grpc</artifactId>
</dependency>
<!-- Dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>${logstash.logback.encoder.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>repackage</id>
<configuration>
<classifier>exec</classifier>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
package br.com.cardif.presales;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application{
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
package br.com.cardif.presales.server.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.micrometer.core.aop.TimedAspect;
import io.micrometer.core.instrument.MeterRegistry;
@Configuration
public class MicrometerConfiguration {
@Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
}
package br.com.cardif.presales.server.controller;
import br.com.cardif.presales.core.domain.PresalesDO;
import br.com.cardif.presales.core.service.PresalesService;
import br.com.cardif.presales.server.controller.request.CreatePresalesRequest;
import br.com.cardif.presales.server.controller.response.PresalesResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.mapstruct.Mapper;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
@RestController
@RequiredArgsConstructor
@RequestMapping("/presales")
public class PresalesRestController {
private final PresalesService service;
private final PresalesMapper mapper;
@PostMapping()
@ResponseStatus( HttpStatus.CREATED)
public PresalesResponse createPresales(@RequestBody @Valid CreatePresalesRequest createPresalesRequest) {
var presalesResponse = mapper.domainToResponse(service.createPresales(mapper.requestToDomain(createPresalesRequest)));
return presalesResponse;
}
@GetMapping("/{id}")
public PresalesResponse getPresalesById(@PathVariable String id) {
var presalesResponse = mapper.domainToResponse(service.findById(id));
return presalesResponse;
}
@Mapper
public interface PresalesMapper {
PresalesDO requestToDomain(CreatePresalesRequest presalesRequest);
PresalesResponse domainToResponse(PresalesDO presalesDO);
}
}
package br.com.cardif.presales.server.controller;
import java.net.URI;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.CollectionUtils;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import br.com.cardif.presales.core.exceptions.BusinessErrorException;
import br.com.cardif.presales.core.exceptions.ResourceNotFoundException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolationException;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
/**
* Global Rest Controller Exception Handler. This handler returns the Error Response Body defined in
* the [RFC 7807](https://datatracker.ietf.org/doc/html/rfc7807) and status codes defined in [RFC
* 7231](https://datatracker.ietf.org/doc/html/rfc7231)
*/
@Slf4j
@RestControllerAdvice
@RequiredArgsConstructor
public class RestServerExceptionHandler {
public static final String LOCATION_FIELD = "location";
public static final String HTTP_MESSAGE_NOT_READABLE = "http.message.not.readable.exception";
public static final String INVALID_FORMAT = "invalid.format.exception";
public static final String JSON_MAPPING = "json.mapping.exception";
public static final String JSON_PARSE = "json.parse.exception";
public static final String HTTP_MEDIA_TYPE_NOT_SUPPORTED =
"http.media.type.not.supported.exception";
public static final String CONSTRAINT_VIOLATION = "constraint.violation.exception";
public static final String METHOD_ARGUMENT_NOT_VALID = "method.argument.not.valid.exception";
private final MessageSource messageSource;
@ExceptionHandler(HttpMessageNotReadableException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ProblemDetail onHttpMessageNotReadableException(HttpMessageNotReadableException e,
HttpServletRequest request) {
String message = getLocalizedMessage(HTTP_MESSAGE_NOT_READABLE);
if (e.getCause() != null) {
log.info("HttpMessageNotReadableException cause:[{}] message:[{}]",
e.getCause().getClass().getName(), e.getCause().getMessage());
if (e.getCause() instanceof HttpMessageNotReadableException) {
message = getLocalizedMessage(HTTP_MESSAGE_NOT_READABLE);
} else if (e.getCause() instanceof InvalidFormatException) {
InvalidFormatException cause = (InvalidFormatException) e.getCause();
message = getLocalizedMessage(INVALID_FORMAT, getPath(cause.getPath()),
cause.getLocation().getLineNr(), cause.getLocation().getColumnNr());
} else if (e.getCause() instanceof JsonMappingException) {
JsonMappingException cause = (JsonMappingException) e.getCause();
message = getLocalizedMessage(JSON_MAPPING, getPath(cause.getPath()),
cause.getLocation().getLineNr(), cause.getLocation().getColumnNr());
} else if (e.getCause() instanceof JsonParseException) {
JsonParseException cause = (JsonParseException) e.getCause();
message = getLocalizedMessage(JSON_PARSE, cause.getLocation().getLineNr(),
cause.getLocation().getColumnNr());
}
}
return defaultErrorResponseBuilder(request, HttpStatus.BAD_REQUEST, message);
}
private String getPath(List<JsonMappingException.Reference> references) {
if (CollectionUtils.isEmpty(references)) {
return "";
} else {
return references.stream().map(ref -> ref.getIndex() != -1 ?
"[" + ref.getIndex() + "]" :
"." + ref.getFieldName()).collect(Collectors.joining()).substring(1);
}
}
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ProblemDetail onHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e,
HttpServletRequest request) {
return defaultErrorResponseBuilder(request, HttpStatus.BAD_REQUEST,
getLocalizedMessage(HTTP_MEDIA_TYPE_NOT_SUPPORTED));
}
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ProblemDetail onConstraintValidationException(ConstraintViolationException e,
HttpServletRequest request) {
// On validating requests with @Valid
List<LocationResponse> details = e.getConstraintViolations().stream()
.map(violation -> new LocationResponse(violation.getPropertyPath().toString(),
violation.getMessage())).collect(Collectors.toList());
ProblemDetail problemDetail = defaultErrorResponseBuilder(request, HttpStatus.BAD_REQUEST,
getLocalizedMessage(CONSTRAINT_VIOLATION));
problemDetail.setProperty(LOCATION_FIELD, details);
return problemDetail;
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ProblemDetail onMethodArgumentNotValidException(MethodArgumentNotValidException e,
HttpServletRequest request) {
// On validating methods with @Validate
List<LocationResponse> details = e.getBindingResult().getFieldErrors().stream()
.map(fieldError -> new LocationResponse(fieldError.getField(),
fieldError.getDefaultMessage())).collect(Collectors.toList());
ProblemDetail problemDetail = defaultErrorResponseBuilder(request, HttpStatus.BAD_REQUEST,
getLocalizedMessage(METHOD_ARGUMENT_NOT_VALID));
problemDetail.setProperty(LOCATION_FIELD, details);
return problemDetail;
}
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ProblemDetail onResourceNotFoundException(ResourceNotFoundException e,
HttpServletRequest request) {
return defaultErrorResponseBuilder(request, HttpStatus.BAD_REQUEST,
getLocalizedMessage(e.getCode(),e.getArgs()));
}
@ExceptionHandler(BusinessErrorException.class)
@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
public ProblemDetail onBusinessErrorException(BusinessErrorException e,
HttpServletRequest request) {
return defaultErrorResponseBuilder(request, HttpStatus.UNPROCESSABLE_ENTITY,
getLocalizedMessage(e.getCode(),e.getArgs()));
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ProblemDetail onException(Exception exception,
HttpServletRequest request) {
log.error("Unhandled Error: ", exception);
return defaultErrorResponseBuilder(request, HttpStatus.INTERNAL_SERVER_ERROR, "");
}
@SneakyThrows
ProblemDetail defaultErrorResponseBuilder(HttpServletRequest request, HttpStatus status,
String message) {
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(status, message);
problemDetail.setInstance(new URI(request.getServletPath()));
problemDetail.setTitle(status.getReasonPhrase());
return problemDetail;
}
String getLocalizedMessage(String code, Object... args) {
Locale local = LocaleContextHolder.getLocale();
return messageSource.getMessage(code, args, local);
}
record LocationResponse(String field, String error) {
}
}
package br.com.cardif.presales.server.controller.request;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class CreatePresalesRequest {
@NotBlank
@Size(max = 20)
private String name;
}
\ No newline at end of file
package br.com.cardif.presales.server.controller.response;
import lombok.Data;
@Data
public class PresalesResponse {
private String id;
private String name;
}
# Server
server.shutdown=graceful
server.port=8080
# Management
management.endpoint.health.show-components=always
management.endpoint.health.show-details=always
# MongoDB
spring.data.mongodb.uri=mongodb://user:pa55word@localhost/presales?authSource=admin
logging.level.br.com.cardif.presales=DEBUG
spring.application.name=book-service
spring.jmx.enabled=false
spring.messages.fallback-to-system-locale=false
spring.messages.use-code-as-default-message=true
# Server
server.shutdown=graceful
# Actuator
management.endpoints.jmx.exposure.exclude=*
management.endpoints.web.exposure.include=health,info,metrics,prometheus
management.info.git.enabled=true
management.info.java.enabled=true
management.info.os.enabled=true
management.metrics.tags.application=${spring.application.name}
# Web
spring.jackson.default-property-inclusion=non_empty
spring.jackson.visibility.field=any
spring.jackson.visibility.getter=none
spring.jackson.visibility.setter=none
spring.jackson.visibility.is-getter=none
logging.level.br.com.cardif.presales=INFO
logging.pattern.level=%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]
\ No newline at end of file
databaseChangeLog:
- changeSet:
id: '01H64BPW5SPDE9D1PKN56JHCPW'
author: malcala@palo-it.com
comment: initialize database
changes:
- tagDatabase:
tag: 1.0.0
- createTable:
tableName: presales
columns:
- column:
name: presales_id
type: VARCHAR(36)
constraints:
primaryKey: true
- column:
name: name
type: VARCHAR(50)
constraints:
nullable: false
- column:
name: status
type: VARCHAR(50)
constraints:
nullable: false
- column:
name: created_datetime
type: DATETIME
constraints:
nullable: false
- column:
name: last_modified_datetime
type: DATETIME
constraints:
nullable: false
- column:
name: version
type: TINYINT
constraints:
nullable: false
\ No newline at end of file
databaseChangeLog:
- includeAll:
path: changes/
relativeToChangelogFile: true
\ No newline at end of file
<configuration>
<springProfile name="local">
<include resource="org/springframework/boot/logging/logback/base.xml"/>
</springProfile>
<springProfile name="default">
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<springProperty name="APP_NAME" source="spring.application.name"/>
<property name="STE_EXCLUSIONS"
value="\$\$FastClassByCGLIB\$\$,\$\$EnhancerBySpringCGLIB\$\$,^sun\.reflect\..*\.invoke,^com\.sun\.,^sun\.net\.,^net\.sf\.cglib\.proxy\.MethodProxy\.invoke,^org\.springframework\.cglib\.,^org\.springframework\.transaction\.,^org\.springframework\.validation\.,^org\.springframework\.app\.,^org\.springframework\.aop\.,^java\.lang\.reflect\.Method\.invoke,^org\.springframework\.ws\..*\.invoke,^org\.springframework\.ws\.transport\.,^org\.springframework\.ws\.soap\.saaj\.SaajSoapMessage\.,^org\.springframework\.ws\.client\.core\.WebServiceTemplate\.,^org\.springframework\.web\.filter\.,^org\.apache\.tomcat\.,^org\.apache\.catalina\.,^org\.apache\.coyote\.,^java\.util\.concurrent\.ThreadPoolExecutor\.runWorker,^java\.lang\.Thread\.run$"/>
<appender name="jsonConsoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<customFields>{"appname":"${APP_NAME}"}</customFields>
<fieldNames>
<version>[ignore]</version>
<level_value>[ignore]</level_value>
</fieldNames>
<throwableConverter class="net.logstash.logback.stacktrace.ShortenedThrowableConverter">
<maxLength>4096</maxLength>
<rootCauseFirst>true</rootCauseFirst>
<exclusions>${STE_EXCLUSIONS}</exclusions>
</throwableConverter>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="jsonConsoleAppender"/>
</root>
</springProfile>
</configuration>
http.message.not.readable.exception=Error reading the request
invalid.format.exception=Error parsing the request:{0} at Line: {1} Column:{2}
json.mapping.exception=Error in field:{0} at Line:{1} Column:{2}
json.parse.exception=Unexpected character at Line:{0} Column:{1}
http.media.type.not.supported.exception=Header content-type no supported
constraint.violation.exception=Error validating the request
method.argument.not.valid.exception=Error during validation
business.error=Error while processing request
resource.not.found.exception=Resource not found
#######################################
# Paste below your custom codes #
# #
# This section must match with codes #
# defined in #
# br.com.cardif.presales.core.exceptions.ErrorCodes.java
#######################################
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment