diff --git a/.m2/settings.xml b/.m2/settings.xml new file mode 100644 index 0000000..510e0ba --- /dev/null +++ b/.m2/settings.xml @@ -0,0 +1,18 @@ + + + org.sonarsource.scanner.maven + + + + sonar + + true + + + + http://localhost:9000 + + + + + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d92b1a3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM adoptopenjdk/openjdk11:alpine-jre + +ARG JAR_FILE=target/calculator.jar + +WORKDIR /opt/app + +COPY ${JAR_FILE} calculator.jar + +COPY entrypoint.sh entrypoint.sh + +RUN chmod 755 entrypoint.sh + +ENTRYPOINT ["./entrypoint.sh"] \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..182e235 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,122 @@ +def CONTAINER_NAME = "calculator" +def ENV_NAME = getEnvName(env.BRANCH_NAME) +def CONTAINER_TAG = getTag(env.BUILD_NUMBER, env.BRANCH_NAME) +def HTTP_PORT = getHTTPPort(env.BRANCH_NAME) +def EMAIL_RECIPIENTS = "philippe.guemkamsimo@gmail.com" + + +node { + try { + stage('Initialize') { + def dockerHome = tool 'DockerLatest' + def mavenHome = tool 'MavenLatest' + env.PATH = "${dockerHome}/bin:${mavenHome}/bin:${env.PATH}" + } + + stage('Checkout') { + checkout scm + } + + stage('Build with test') { + + sh "mvn clean install" + } + + stage('Sonarqube Analysis') { + withSonarQubeEnv('SonarQubeLocalServer') { + sh " mvn sonar:sonar -Dintegration-tests.skip=true -Dmaven.test.failure.ignore=true" + } + timeout(time: 1, unit: 'MINUTES') { + def qg = waitForQualityGate() // Reuse taskId previously collected by withSonarQubeEnv + if (qg.status != 'OK') { + error "Pipeline aborted due to quality gate failure: ${qg.status}" + } + } + } + + stage("Image Prune") { + imagePrune(CONTAINER_NAME) + } + + stage('Image Build') { + imageBuild(CONTAINER_NAME, CONTAINER_TAG) + } + + stage('Push to Docker Registry') { + withCredentials([usernamePassword(credentialsId: 'DockerhubCredentials', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) { + pushToImage(CONTAINER_NAME, CONTAINER_TAG, USERNAME, PASSWORD) + } + } + + stage('Run App') { + withCredentials([usernamePassword(credentialsId: 'DockerhubCredentials', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) { + runApp(CONTAINER_NAME, CONTAINER_TAG, USERNAME, HTTP_PORT, ENV_NAME) + + } + } + + } finally { + deleteDir() + sendEmail(EMAIL_RECIPIENTS); + } + +} + +def imagePrune(containerName) { + try { + sh "docker image prune -f" + sh "docker stop $containerName" + } catch (ignored) { + } +} + +def imageBuild(containerName, tag) { + sh "docker build -t $containerName:$tag -t $containerName --pull --no-cache ." + echo "Image build complete" +} + +def pushToImage(containerName, tag, dockerUser, dockerPassword) { + sh "docker login -u $dockerUser -p $dockerPassword" + sh "docker tag $containerName:$tag $dockerUser/$containerName:$tag" + sh "docker push $dockerUser/$containerName:$tag" + echo "Image push complete" +} + +def runApp(containerName, tag, dockerHubUser, httpPort, envName) { + sh "docker pull $dockerHubUser/$containerName" + sh "docker run --rm --env SPRING_ACTIVE_PROFILES=$envName -d -p $httpPort:$httpPort --name $containerName $dockerHubUser/$containerName:$tag" + echo "Application started on port: ${httpPort} (http)" +} + +def sendEmail(recipients) { + mail( + to: recipients, + subject: "Build ${env.BUILD_NUMBER} - ${currentBuild.currentResult} - (${currentBuild.fullDisplayName})", + body: "Check console output at: ${env.BUILD_URL}/console" + "\n") +} + +String getEnvName(String branchName) { + if (branchName == 'main') { + return 'prod' + } else if (branchName.startsWith("release-") || branchName.startsWith("hotfix-") || branchName == 'ready') { + return 'uat' + } + return 'dev' +} + +String getHTTPPort(String branchName) { + if (branchName == 'main') { + return '9001' + + } else if (branchName.startsWith("release-") || branchName.startsWith("hotfix-") || branchName == 'ready') { + return '9002' + } + return '9003' +} + +String getTag(String buildNumber, String branchName) { + if (branchName == 'main') { + return buildNumber + '-unstable' + } + return buildNumber + '-stable' +} diff --git a/README.md b/README.md index 4cfab93..814e5de 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,191 @@ # Dépôt des sources JAVA pour cours CICD -Sources accompagnant le cours "Continuous Integration, Continuous Delivery" + + +1/ Préparer les machines distantes + + + +a/ Démarrer 2 machine linux + + +i/ +docker run -it --name mysql-alpine-dev --rm alpine-ssh-server + +docker run -it --name mysql-alpine-uat --rm alpine-ssh-server + +Pour chacune des machines: + +Modifier le mot de passe root: + + passwd + my-secret-pw + + + +Démarrez le service ssh: + +/etc/init.d/sshd restart + + +Vérifier l'état du service SSH + +netstat -atunp +Si aucun service n'est en mode LISTEN sur le port 22 alors redémarrez + + +Noter l'adresse IP de l'interface eth0: +ifconfig + +172.17.0.4 + +172.17.0.5 + + + +============ (pas nécessaire sauf si vous partez d'une machine alpine simple) =========== + +b/ Install OpenSSH et ses outils pour la communication via SSH + +apk add python3 +apk add openssh +apk add nano + + +c/ Démarrer le serveur ssh: + + apk add openrc + + rc-update add sshd + + /etc/init.d/sshd start + + touch /run/openrc/softlevel + + /etc/init.d/sshd start + + rc-status + + netstat -atunp + + -- + nano /etc/ssh/sshd_config + + Append (a) + + PermitRootLogin Yes + + exit(esc) => :wq + + /etc/init.d/sshd restart + -- + +Modifier le mot de passe root: + + passwd + my-secret-pw + +======================================================== + + + +======== Pas nécessaire, je l'ai utilisée pour sauvegarder les machines déjà configurées ================== + +c/ Clone the previously created machine for the second machine + +docker commit d3e616f327a4 alpine-ssh-server + + +docker tag alpine-ssh-server imzerofiltre/alpine-ssh-server + +docker push imzerofiltre/alpine-ssh-server + +================================================= + + + + +2/ Préparer la machine de contrôle + + +a/ Démarrer une machine linux + +docker run -it --name ansible-controller alpine + + + + +b/ Install OpenSSH for SSH comminucation between the remote and the controller + + apk add openssh + + + +Ajouter les clés de chaque machine en tant que serveurs connus + +ssh @IP machine distante + + +c/ Générer une paire de clé privé/publique à utiliser en lieu et place du mot de passe + + + + ssh-keygen + (Utiliser les valeurs par défaut en tapant juste entrer jusqu'à la fin) + + +c/ Importer la clé publique SSH de la machine de contrôle: + + ssh-copy-id @IP machine distante + + ssh @IP machine distante => désormais plus de clé demandée. + +b/ Installer Ansible + + apk add ansible + + Vérifier l'installation avec: + + ansible --version + + + + +d/ Définissions l'inventaire (inventory) + +apk add nano + +mkdir /etc/ansible +nano /etc/ansible/host + + +[database] +172.17.0.9 +172.17.0.10 + + +Ecrire le playbook + +nano /etc/ansible/install-db.yaml + +- name: install db servers play + hosts: databases + tasks: + - name: Install server and client + command: apk add mysql mysql-client + + - name: Pause 10 seconds for installation process to finish + pause: seconds=10 + + - name: Setup database + command: /etc/init.d/mariadb setup + + - name: Start the server + command: /etc/init.d/mariadb start + + + +Démarrer le playbook : + +ansible-playbook -i /etc/ansible/host /etc/ansible/install-db.yaml + diff --git a/derby.log b/derby.log new file mode 100644 index 0000000..e69de29 diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..4d68d33 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +echo "The app is starting ..." +exec java -jar -Dspring.profiles.active=${SPRING_ACTIVE_PROFILES} "calculator.jar" \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0c27268..e77961a 100644 --- a/pom.xml +++ b/pom.xml @@ -1,97 +1,242 @@ - 4.0.0 - com.openclassrooms.testing - calculator - 0.0.1-SNAPSHOT - - 5.5.2 - UTF-8 - 11 - ${maven.compiler.source} - 4.3.1 - 2.2.0.RELEASE - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + tech.zerofiltre.testing + calculator + 0.0.1-SNAPSHOT + + 5.5.2 + UTF-8 + 11 + ${maven.compiler.source} + 4.3.1 + 2.2.0.RELEASE + tech.zerofiltre.testing:calculator + + **/domain/**/*.java, + src/test/java/*.java + - - org.springframework.boot - spring-boot-starter-parent - 2.2.0.RELEASE - + 5.4.24.Final + 4.2.0 + 4.2.0 + 4.1.1 + 2.0.1.Final + 3.27.0-GA + 2.4.0-b180830.0359 + yyyy-MM-dd_HH-mm - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-test - - - org.springframework.boot - spring-boot-starter-thymeleaf - - - org.springframework.boot - spring-boot-devtools - runtime - - - org.webjars - bootstrap - ${bootstrap.version} - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter - test - - - org.mockito - mockito-junit-jupiter - test - - - org.assertj - assertj-core - test - - - org.apache.logging.log4j - log4j-core - - - javax.inject - javax.inject - 1 - - - - calculator - - - org.springframework.boot - spring-boot-maven-plugin - - - org.apache.maven.plugins - maven-failsafe-plugin - - - - integration-test - verify - - - - - - + + + + org.springframework.boot + spring-boot-starter-parent + 2.2.0.RELEASE + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-devtools + runtime + + + + com.h2database + h2 + runtime + + + org.webjars + bootstrap + ${bootstrap.version} + + + org.liquibase + liquibase-core + ${liquibase-core.version} + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter + test + + + org.mockito + mockito-junit-jupiter + test + + + org.assertj + assertj-core + test + + + org.apache.logging.log4j + log4j-core + + + javax.inject + javax.inject + 1 + + + org.projectlombok + lombok + 1.18.20 + provided + + + org.seleniumhq.selenium + selenium-java + test + + + io.github.bonigarcia + webdrivermanager + 3.7.1 + + + mysql + mysql-connector-java + + + + + + dev + + true + + + dev + + + + + uat + + uat + + + + + prod + + prod + + + + + + + calculator + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.2 + + + + prepare-agent + + + + + report + test + + report + + + + + + org.liquibase + liquibase-maven-plugin + ${liquibase-maven-plugin.version} + + src/main/resources/liquibase.properties + src/main/resources/db/changelog/db.changelog-${activeProfile}.yaml + src/main/resources/db/changelog/db.changelog-${activeProfile}.yaml + + src/main/resources/db/changelog/changes/${activeProfile}/${maven.build.timestamp}_changelog.yaml + + info + + + + org.liquibase + liquibase-core + ${liquibase-core.version} + + + org.liquibase.ext + liquibase-hibernate5 + ${liquibase-hibernate5.version} + + + org.springframework.boot + spring-boot-starter-data-jpa + ${springboot.version} + + + javax.validation + validation-api + ${validation-api.version} + + + org.javassist + javassist + ${javassist.version} + + + javax.xml.bind + jaxb-api + ${jaxb-api.version} + + + + + \ No newline at end of file diff --git a/src/main/java/tech/zerofiltre/testing/calcul/CalculatorApp.java b/src/main/java/tech/zerofiltre/testing/calcul/CalculatorApp.java index 2dd9867..fa36566 100644 --- a/src/main/java/tech/zerofiltre/testing/calcul/CalculatorApp.java +++ b/src/main/java/tech/zerofiltre/testing/calcul/CalculatorApp.java @@ -6,7 +6,7 @@ @SpringBootApplication public class CalculatorApp { - public static void main(String[] args) { - SpringApplication.run(CalculatorApp.class, args); - } + public static void main(String[] args) { + SpringApplication.run(CalculatorApp.class, args); + } } diff --git a/src/main/java/tech/zerofiltre/testing/calcul/controller/CalculatorController.java b/src/main/java/tech/zerofiltre/testing/calcul/controller/CalculatorController.java index f89c344..36f5c3e 100644 --- a/src/main/java/tech/zerofiltre/testing/calcul/controller/CalculatorController.java +++ b/src/main/java/tech/zerofiltre/testing/calcul/controller/CalculatorController.java @@ -2,48 +2,56 @@ import javax.inject.Inject; import javax.validation.Valid; - import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; - import tech.zerofiltre.testing.calcul.domain.model.Calculation; import tech.zerofiltre.testing.calcul.domain.model.CalculationModel; import tech.zerofiltre.testing.calcul.domain.model.CalculationType; +import tech.zerofiltre.testing.calcul.repository.CalculationModelRepository; import tech.zerofiltre.testing.calcul.service.CalculatorService; @Controller public class CalculatorController { - public static final String CALCULATOR_TEMPLATE = "calculator"; + public static final String CALCULATOR_TEMPLATE = "calculator"; + + CalculatorService calculatorService; + + CalculationModelRepository calculationModelRepository; + + public CalculatorController(CalculatorService calculatorService, + CalculationModelRepository calculationModelRepository) { + this.calculatorService = calculatorService; + this.calculationModelRepository = calculationModelRepository; + } - @Inject - CalculatorService calculatorService; + @GetMapping("/") + public String index(Calculation calculation) { + return "redirect:/calculator"; + } - @GetMapping("/") - public String index(Calculation calculation) { - return "redirect:/calculator"; - } + @GetMapping("/calculator") + public String root(Calculation calculation) { + return CALCULATOR_TEMPLATE; // cf. resources/templates/calculator.html + } - @GetMapping("/calculator") - public String root(Calculation calculation) { - return CALCULATOR_TEMPLATE; // cf. resources/templates/calculator.html - } + @PostMapping("/calculator") + public String calculate(@Valid Calculation calculation, BindingResult bindingResult, Model model) { - @PostMapping("/calculator") - public String calculate(@Valid Calculation calculation, BindingResult bindingResult, Model model) { + final CalculationType type = CalculationType.valueOf(calculation.getCalculationType()); + final CalculationModel calculationModel = new CalculationModel( + type, + calculation.getLeftArgument(), + calculation.getRightArgument()); - final CalculationType type = CalculationType.valueOf(calculation.getCalculationType()); - final CalculationModel calculationModel = new CalculationModel( - type, - calculation.getLeftArgument(), - calculation.getRightArgument()); + final CalculationModel response = calculatorService.calculate(calculationModel); - final CalculationModel response = calculatorService.calculate(calculationModel); + calculationModelRepository.save(response); - model.addAttribute("response", response); - return CALCULATOR_TEMPLATE; // cf. resources/templates/calculator.html - } + model.addAttribute("response", response); + return CALCULATOR_TEMPLATE; // cf. resources/templates/calculator.html + } } diff --git a/src/main/java/tech/zerofiltre/testing/calcul/domain/Calculator.java b/src/main/java/tech/zerofiltre/testing/calcul/domain/Calculator.java index 6492e44..afe011e 100644 --- a/src/main/java/tech/zerofiltre/testing/calcul/domain/Calculator.java +++ b/src/main/java/tech/zerofiltre/testing/calcul/domain/Calculator.java @@ -4,10 +4,14 @@ import java.util.Set; import javax.inject.Named; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @Named public class Calculator { + Logger logger = LoggerFactory.getLogger(Calculator.class); + public int add(int a, int b) { return a + b; } @@ -54,12 +58,12 @@ public void longCalculation() { try { Thread.sleep(500); } catch (final InterruptedException e) { - e.printStackTrace(); + logger.debug("Thread has been interrupted", e); } } public Set digitsSet(int number) { - final Set integers = new HashSet(); + final Set integers = new HashSet<>(); final String numberString = String.valueOf(number); for (int i = 0; i < numberString.length(); i++) { diff --git a/src/main/java/tech/zerofiltre/testing/calcul/domain/model/CalculationModel.java b/src/main/java/tech/zerofiltre/testing/calcul/domain/model/CalculationModel.java index 0e6599a..510e06e 100644 --- a/src/main/java/tech/zerofiltre/testing/calcul/domain/model/CalculationModel.java +++ b/src/main/java/tech/zerofiltre/testing/calcul/domain/model/CalculationModel.java @@ -1,86 +1,67 @@ package tech.zerofiltre.testing.calcul.domain.model; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Transient; +import lombok.Data; +import lombok.NoArgsConstructor; + /** - * A model to represent a two argument integer calculation which needs to be - * performed. - * + * A model to represent a two argument integer calculation which needs to be performed. */ +@Data +@Entity +@NoArgsConstructor public class CalculationModel { - private static final String SEPARATOR = " "; - private Integer leftArgument; - private Integer rightArgument; - private CalculationType type; - private Integer solution; - private String formattedSolution; - - public CalculationModel(CalculationType calculationType, int leftArgument, int rightArgument) { - type = calculationType; - this.leftArgument = leftArgument; - this.rightArgument = rightArgument; - } - - /** - * Convenience Constructor used in test - */ - public CalculationModel(CalculationType calculationType, int leftArgument, int rightArgument, Integer solution) { - type = calculationType; - this.leftArgument = leftArgument; - this.rightArgument = rightArgument; - this.solution = solution; - } - - /** - * Builds a Calculation from a string such as 2 + 2 - * - * @param calculation in written form - * @return model representing the calculatoin - */ - public static CalculationModel fromText(String calculation) { - final String[] parts = calculation.split(SEPARATOR); - final int leftArgument = Integer.parseInt(parts[0]); - final int rightArgument = Integer.parseInt(parts[2]); - final CalculationType calculationType = CalculationType.fromSymbol(parts[1]); - - return new CalculationModel(calculationType, leftArgument, rightArgument); - } - - public Integer getLeftArgument() { - return leftArgument; - } - public void setLeftArgument(Integer leftArgument) { - this.leftArgument = leftArgument; - } + private static final String SEPARATOR = " "; - public Integer getRightArgument() { - return rightArgument; - } + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; - public void setRightArgument(Integer rightArgument) { - this.rightArgument = rightArgument; - } + private Integer leftArgument; + private Integer rightArgument; + @Transient + private CalculationType type; + private Integer solution; + private String formattedSolution; + private String typeSymbol; - public CalculationType getType() { - return type; - } + public CalculationModel(CalculationType calculationType, int leftArgument, int rightArgument) { + type = calculationType; + this.leftArgument = leftArgument; + this.rightArgument = rightArgument; + this.typeSymbol = CalculationType.toSymbol(calculationType); - public void setType(CalculationType type) { - this.type = type; - } + } - public Integer getSolution() { - return solution; - } + /** + * Convenience Constructor used in test + */ + public CalculationModel(CalculationType calculationType, int leftArgument, int rightArgument, Integer solution) { + type = calculationType; + this.leftArgument = leftArgument; + this.rightArgument = rightArgument; + this.solution = solution; + this.typeSymbol = CalculationType.toSymbol(calculationType); + } - public void setSolution(Integer solution) { - this.solution = solution; - } + /** + * Builds a Calculation from a string such as 2 + 2 + * + * @param calculation in written form + * @return model representing the calculatoin + */ + public static CalculationModel fromText(String calculation) { + final String[] parts = calculation.split(SEPARATOR); + final int leftArgument = Integer.parseInt(parts[0]); + final int rightArgument = Integer.parseInt(parts[2]); + final CalculationType calculationType = CalculationType.fromSymbol(parts[1]); - public String getFormattedSolution() { - return formattedSolution; - } + return new CalculationModel(calculationType, leftArgument, rightArgument); + } - public void setFormattedSolution(String formattedSolution) { - this.formattedSolution = formattedSolution; - } } diff --git a/src/main/java/tech/zerofiltre/testing/calcul/domain/model/CalculationType.java b/src/main/java/tech/zerofiltre/testing/calcul/domain/model/CalculationType.java index 40f83d8..d7d561a 100644 --- a/src/main/java/tech/zerofiltre/testing/calcul/domain/model/CalculationType.java +++ b/src/main/java/tech/zerofiltre/testing/calcul/domain/model/CalculationType.java @@ -1,26 +1,41 @@ package tech.zerofiltre.testing.calcul.domain.model; public enum CalculationType { - ADDITION, - MULTIPLICATION, - DIVISION, - SUBTRACTION, - CONVERSION; + ADDITION, + MULTIPLICATION, + DIVISION, + SUBTRACTION, + CONVERSION; - public static CalculationType fromSymbol(String operation) { - switch (operation) { - case "+": - return ADDITION; - case "-": - return SUBTRACTION; - case "/": - return DIVISION; - case "*": - return MULTIPLICATION; - case "x": - return MULTIPLICATION; - default: - throw new UnsupportedOperationException("Not implemented yet"); - } + public static CalculationType fromSymbol(String operation) { + switch (operation) { + case "+": + return ADDITION; + case "-": + return SUBTRACTION; + case "/": + return DIVISION; + case "*": + return MULTIPLICATION; + case "x": + return MULTIPLICATION; + default: + throw new UnsupportedOperationException("Not implemented yet"); } + } + + public static String toSymbol(CalculationType type) { + switch (type) { + case ADDITION: + return "+"; + case SUBTRACTION: + return "-"; + case DIVISION: + return "/"; + case MULTIPLICATION: + return "*"; + default: + throw new UnsupportedOperationException("Not implemented yet"); + } + } } diff --git a/src/main/java/tech/zerofiltre/testing/calcul/repository/CalculationModelRepository.java b/src/main/java/tech/zerofiltre/testing/calcul/repository/CalculationModelRepository.java new file mode 100644 index 0000000..02a080b --- /dev/null +++ b/src/main/java/tech/zerofiltre/testing/calcul/repository/CalculationModelRepository.java @@ -0,0 +1,8 @@ +package tech.zerofiltre.testing.calcul.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import tech.zerofiltre.testing.calcul.domain.model.CalculationModel; + +public interface CalculationModelRepository extends JpaRepository { + +} diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml new file mode 100644 index 0000000..aa434c2 --- /dev/null +++ b/src/main/resources/application-dev.yaml @@ -0,0 +1,16 @@ +spring: + datasource: + url: jdbc:mysql://host.docker.internal:3307/calculator?serverTimezone=UTC + username: root + password: my-secret-pw + + jpa: + hibernate: + ddl-auto: none + liquibase: + enabled: true + change-log: classpath:/db/changelog/db.changelog-dev.yaml + + +server: + port: 9003 \ No newline at end of file diff --git a/src/main/resources/application-prod.yaml b/src/main/resources/application-prod.yaml new file mode 100644 index 0000000..54aec90 --- /dev/null +++ b/src/main/resources/application-prod.yaml @@ -0,0 +1,15 @@ +spring: + datasource: + url: jdbc:mysql://host.docker.internal:3309/calculator?serverTimezone=UTC + username: root + password: my-secret-pw + + jpa: + hibernate: + ddl-auto: none + liquibase: + enabled: true + change-log: classpath:/db/changelog/db.changelog-prod.yaml + +server: + port: 9001 \ No newline at end of file diff --git a/src/main/resources/application-uat.yaml b/src/main/resources/application-uat.yaml new file mode 100644 index 0000000..25dc0bd --- /dev/null +++ b/src/main/resources/application-uat.yaml @@ -0,0 +1,15 @@ +spring: + datasource: + url: jdbc:mysql://host.docker.internal:3308/calculator?serverTimezone=UTC + username: root + password: my-secret-pw + + jpa: + hibernate: + ddl-auto: none + liquibase: + enabled: true + change-log: classpath:/db/changelog/db.changelog-uat.yaml + +server: + port: 9002 \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 0000000..36313ac --- /dev/null +++ b/src/main/resources/application.yaml @@ -0,0 +1,16 @@ +spring: + datasource: + url: jdbc:mysql://localhost:3306/calculator?serverTimezone=UTC + username: root + password: my-secret-pw + + jpa: + hibernate: + ddl-auto: update + + #disable liquibase at start-up + liquibase: + enabled: false + +server: + port: 8999 \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/.gitkeep b/src/main/resources/db/changelog/changes/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/db/changelog/db.changelog-dev.yaml b/src/main/resources/db/changelog/db.changelog-dev.yaml new file mode 100644 index 0000000..0310fd0 --- /dev/null +++ b/src/main/resources/db/changelog/db.changelog-dev.yaml @@ -0,0 +1,3 @@ +databaseChangeLog: + - includeAll: + path: db/changelog/changes/dev \ No newline at end of file diff --git a/src/main/resources/db/changelog/db.changelog-prod.yaml b/src/main/resources/db/changelog/db.changelog-prod.yaml new file mode 100644 index 0000000..108e535 --- /dev/null +++ b/src/main/resources/db/changelog/db.changelog-prod.yaml @@ -0,0 +1,3 @@ +databaseChangeLog: + - includeAll: + path: db/changelog/changes/prod \ No newline at end of file diff --git a/src/main/resources/db/changelog/db.changelog-uat.yaml b/src/main/resources/db/changelog/db.changelog-uat.yaml new file mode 100644 index 0000000..dd13535 --- /dev/null +++ b/src/main/resources/db/changelog/db.changelog-uat.yaml @@ -0,0 +1,3 @@ +databaseChangeLog: + - includeAll: + path: db/changelog/changes/uat \ No newline at end of file diff --git a/src/main/resources/liquibase.properties b/src/main/resources/liquibase.properties new file mode 100644 index 0000000..a12db58 --- /dev/null +++ b/src/main/resources/liquibase.properties @@ -0,0 +1,14 @@ + +#### Database properties +url=${liquibase.url} +username=${liquibase.username} +password=${liquibase.password} +driver=com.mysql.cj.jdbc.Driver + +#### Reference database properties +referenceUrl=${liquibase.referenceUrl} +referenceDriver=com.mysql.cj.jdbc.Driver +referenceUsername=${liquibase.referenceUsername} +referencePassword=${liquibase.referencePassword} + +diffTypes=tables, views, columns, indexes, foreignkeys, primarykeys, uniqueconstraints, data \ No newline at end of file diff --git a/src/main/resources/templates/calculator.html b/src/main/resources/templates/calculator.html index 9174951..277575c 100644 --- a/src/main/resources/templates/calculator.html +++ b/src/main/resources/templates/calculator.html @@ -28,10 +28,10 @@

- +
- @@ -39,12 +39,12 @@

- +
- +
diff --git a/src/test/java/tech/zerofiltre/testing/calcul/controller/CalculatorControllerSIT.java b/src/test/java/tech/zerofiltre/testing/calcul/controller/CalculatorControllerSIT.java index 382bbe7..a9a26ca 100644 --- a/src/test/java/tech/zerofiltre/testing/calcul/controller/CalculatorControllerSIT.java +++ b/src/test/java/tech/zerofiltre/testing/calcul/controller/CalculatorControllerSIT.java @@ -1,57 +1,73 @@ package tech.zerofiltre.testing.calcul.controller; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import javax.inject.Inject; - +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; - +import org.springframework.test.web.servlet.setup.MockMvcBuilders; import tech.zerofiltre.testing.calcul.domain.Calculator; +import tech.zerofiltre.testing.calcul.domain.model.CalculationModel; +import tech.zerofiltre.testing.calcul.repository.CalculationModelRepository; import tech.zerofiltre.testing.calcul.service.CalculatorService; +import tech.zerofiltre.testing.calcul.service.CalculatorServiceImpl; import tech.zerofiltre.testing.calcul.service.SolutionFormatter; -@WebMvcTest(controllers = { CalculatorController.class, CalculatorService.class }) +@Disabled @ExtendWith(SpringExtension.class) -public class CalculatorControllerSIT { - - @Inject - private MockMvc mockMvc; - - @MockBean - private SolutionFormatter solutionFormatter; - - @MockBean - private Calculator calculator; - - @Test - public void givenACalculatorApp_whenRequestToAdd_thenSolutionIsShown() throws Exception { - // GIVEN - when(calculator.add(2, 3)).thenReturn(5); - - // WHEN - final MvcResult result = mockMvc.perform( - MockMvcRequestBuilders.post("/calculator") - .param("leftArgument", "2") - .param("rightArgument", "3") - .param("calculationType", "ADDITION")) - .andExpect(MockMvcResultMatchers.status().is2xxSuccessful()) - .andReturn(); - - // THEN - assertThat(result.getResponse().getContentAsString()) - .contains("id=\"solution\"") - .contains(">55 actualDigits = calculatorUnderTest.digitsSet(number); assertThat(actualDigits).containsExactlyInAnyOrder(1, 2, 3, 4); } @Test - public void listDigits_shouldReturnsTheListOfZero_ofZero() { + void listDigits_shouldReturnsTheListOfZero_ofZero() { final int number = 0; final Set actualDigits = calculatorUnderTest.digitsSet(number); assertThat(actualDigits).containsExactly(0); @@ -158,7 +158,7 @@ public void listDigits_shouldReturnsTheListOfZero_ofZero() { @Disabled("Stoppé car cela échoue tous les mardis") @Test - public void testDate() { + void testDate() { // GIVEN final LocalDateTime dateTime = LocalDateTime.now(); @@ -169,7 +169,7 @@ public void testDate() { } @Test - public void fact12_shouldReturnsTheCorrectAnswer() { + void fact12_shouldReturnsTheCorrectAnswer() { // GIVEN final int number = 12; @@ -182,7 +182,7 @@ public void fact12_shouldReturnsTheCorrectAnswer() { } @Test - public void digitsSetOfFact12_shouldReturnsTheCorrectAnswser() { + void digitsSetOfFact12_shouldReturnsTheCorrectAnswser() { // GIVEN final int cacheFactorial = 479001600; @@ -194,7 +194,7 @@ public void digitsSetOfFact12_shouldReturnsTheCorrectAnswser() { } @Test - public void multiplyAndDivide_shouldBeIdentity() { + void multiplyAndDivide_shouldBeIdentity() { // GIVEN final Random r = new Random(); final int a = 1 + r.nextInt(100); diff --git a/src/test/java/tech/zerofiltre/testing/calcul/domain/ConversionCalculatorTest.java b/src/test/java/tech/zerofiltre/testing/calcul/domain/ConversionCalculatorTest.java index 1fb80e8..188e0a8 100644 --- a/src/test/java/tech/zerofiltre/testing/calcul/domain/ConversionCalculatorTest.java +++ b/src/test/java/tech/zerofiltre/testing/calcul/domain/ConversionCalculatorTest.java @@ -12,7 +12,7 @@ @Tag("ConversionTests") @DisplayName("Réussir à convertir entre différentes unités.") -public class ConversionCalculatorTest { + class ConversionCalculatorTest { private ConversionCalculator calculatorUnderTest = new ConversionCalculator(); @@ -22,14 +22,14 @@ public class ConversionCalculatorTest { class TemperatureTests { @Test @DisplayName("Soit une T° à 0°C, lorsque l'on convertit en °F, alors on obtient 32°F.") - public void celsiusToFahrenheit_returnsAFahrenheitTempurature_whenCelsiusIsZero() { + void celsiusToFahrenheit_returnsAFahrenheitTempurature_whenCelsiusIsZero() { Double actualFahrenheit = calculatorUnderTest.celsiusToFahrenheit(0.0); assertThat(actualFahrenheit).isCloseTo(32.0, withinPercentage(0.01)); } @Test @DisplayName("Soit une T° à 32°F, lorsque l'on convertit en °C, alors on obtient 0°C.") - public void fahrenheitToCelsius_returnsZeroCelciusTempurature_whenThirtyTwo() { + void fahrenheitToCelsius_returnsZeroCelciusTempurature_whenThirtyTwo() { Double actualCelsius = calculatorUnderTest.fahrenheitToCelsius(32.0); assertThat(actualCelsius).isCloseTo(0.0, withinPercentage(0.01)); } @@ -37,14 +37,14 @@ public void fahrenheitToCelsius_returnsZeroCelciusTempurature_whenThirtyTwo() { @Test @DisplayName("Soit un volume de 3.78541 litres, en gallons, on obtient 1 gallon.") - public void litresToGallons_returnsOneGallon_whenConvertingTheEquivalentLitres() { + void litresToGallons_returnsOneGallon_whenConvertingTheEquivalentLitres() { Double actualLitres = calculatorUnderTest.litresToGallons(3.78541); assertThat(actualLitres).isCloseTo(1.0, withinPercentage(0.01)); } @Test @DisplayName("L'aire d'un disque de rayon 1 doit valoir PI.") - public void radiusToAreaOfCircle_returnsPi_whenWeHaveARadiusOfOne() { + void radiusToAreaOfCircle_returnsPi_whenWeHaveARadiusOfOne() { Double actualArea = calculatorUnderTest.radiusToAreaOfCircle(1.0); assertThat(actualArea).isCloseTo(PI, withinPercentage(0.01)); } diff --git a/src/test/java/tech/zerofiltre/testing/calcul/domain/DoubleCalculatorTest.java b/src/test/java/tech/zerofiltre/testing/calcul/domain/DoubleCalculatorTest.java index 31abf37..a86975f 100644 --- a/src/test/java/tech/zerofiltre/testing/calcul/domain/DoubleCalculatorTest.java +++ b/src/test/java/tech/zerofiltre/testing/calcul/domain/DoubleCalculatorTest.java @@ -4,17 +4,17 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -public class DoubleCalculatorTest { + class DoubleCalculatorTest { private Calculator calculatorUnderTest; @BeforeEach - public void initCalculator() { + void initCalculator() { calculatorUnderTest = new Calculator(); } @Test - @Disabled("Test ambigu et hors limite du type double") - public void subTwoDoubleNumbers_shouldReturnsTheCorrectAnswer() { + @Disabled("Test ambigue et hors limite du type double") + void subTwoDoubleNumbers_shouldReturnsTheCorrectAnswer() { // GIVEN // WHEN diff --git a/src/test/java/tech/zerofiltre/testing/calcul/domain/StatisticsCalculatorTest.java b/src/test/java/tech/zerofiltre/testing/calcul/domain/StatisticsCalculatorTest.java index a798cd2..06df414 100644 --- a/src/test/java/tech/zerofiltre/testing/calcul/domain/StatisticsCalculatorTest.java +++ b/src/test/java/tech/zerofiltre/testing/calcul/domain/StatisticsCalculatorTest.java @@ -16,7 +16,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -public class StatisticsCalculatorTest { + class StatisticsCalculatorTest { @Spy IntSummaryStatistics summaryStatistics = new IntSummaryStatistics(); @@ -24,12 +24,12 @@ public class StatisticsCalculatorTest { StatisticsCalculator underTest; @BeforeEach - public void setUp() { + void setUp() { underTest = new StatisticsCalculator(summaryStatistics); } @Test - public void average_shouldSample_allIntegersProvided() { + void average_shouldSample_allIntegersProvided() { final ArgumentCaptor sampleCaptor = ArgumentCaptor.forClass(Integer.class); final List samples = Arrays.asList(2, 8, 5, 3, 7); @@ -41,7 +41,7 @@ public void average_shouldSample_allIntegersProvided() { } @Test - public void average_shouldReturnTheMean_ofAListOfIntegers() { + void average_shouldReturnTheMean_ofAListOfIntegers() { final List samples = Arrays.asList(2, 8, 5, 3, 7); final Integer result = underTest.average(samples); diff --git a/src/test/java/tech/zerofiltre/testing/calcul/e2e/MultiplicationJourneyE2E.java b/src/test/java/tech/zerofiltre/testing/calcul/e2e/MultiplicationJourneyE2E.java new file mode 100644 index 0000000..24eac6f --- /dev/null +++ b/src/test/java/tech/zerofiltre/testing/calcul/e2e/MultiplicationJourneyE2E.java @@ -0,0 +1,72 @@ +package tech.zerofiltre.testing.calcul.e2e; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.github.bonigarcia.wdm.WebDriverManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.firefox.FirefoxDriver; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.web.server.LocalServerPort; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +class MultiplicationJourneyE2E { + + @LocalServerPort + private int port; + + private WebDriver webDriver; + private String baseUrl; + + @BeforeAll + static void setUpFireFoxDriver() { + WebDriverManager.firefoxdriver().setup(); + } + + @BeforeEach + void setUpWebDriver() { + webDriver = new FirefoxDriver(); + baseUrl = "http://localhost:" + port + "/calculator"; + + } + + @AfterEach + void quitWebDriver() { + if (webDriver != null) { + webDriver.quit(); + } + } + + @Test + void multiplyTwoBySixteenMustReturn32() { + + //GIVEN + webDriver.get(baseUrl); + WebElement leftField = webDriver.findElement(By.id("left")); + WebElement typeDropDown = webDriver.findElement(By.id("type")); + WebElement rightField = webDriver.findElement(By.id("right")); + WebElement submitButton = webDriver.findElement(By.id("submit")); + + //WHEN + leftField.sendKeys("2"); + typeDropDown.sendKeys("x"); + rightField.sendKeys("16"); + submitButton.click(); + + //THEN + WebDriverWait waiter = new WebDriverWait(webDriver, 5); + WebElement solutionElement = waiter.until(ExpectedConditions.presenceOfElementLocated(By.id("solution"))); + String solution = solutionElement.getText(); + assertThat(solution).isEqualTo("32"); + } + + +} diff --git a/src/test/java/tech/zerofiltre/testing/calcul/repository/CalculationModelRepositorySIT.java b/src/test/java/tech/zerofiltre/testing/calcul/repository/CalculationModelRepositorySIT.java new file mode 100644 index 0000000..955100d --- /dev/null +++ b/src/test/java/tech/zerofiltre/testing/calcul/repository/CalculationModelRepositorySIT.java @@ -0,0 +1,24 @@ +package tech.zerofiltre.testing.calcul.repository; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import tech.zerofiltre.testing.calcul.domain.model.CalculationModel; +import tech.zerofiltre.testing.calcul.domain.model.CalculationType; + +@DataJpaTest +public class CalculationModelRepositorySIT { + + @Autowired + CalculationModelRepository calculationModelRepository; + + @Test + void save_mustGenerateId() { + CalculationModel calculationModel = new CalculationModel(CalculationType.ADDITION, 1, 2, 3); + calculationModelRepository.save(calculationModel); + assertThat(calculationModel.getId()).isNotNull(); + } + +} diff --git a/src/test/java/tech/zerofiltre/testing/calcul/service/BatchCalculatorServiceTest.java b/src/test/java/tech/zerofiltre/testing/calcul/service/BatchCalculatorServiceTest.java index 55f708f..89e9b91 100644 --- a/src/test/java/tech/zerofiltre/testing/calcul/service/BatchCalculatorServiceTest.java +++ b/src/test/java/tech/zerofiltre/testing/calcul/service/BatchCalculatorServiceTest.java @@ -25,7 +25,7 @@ import tech.zerofiltre.testing.calcul.domain.model.CalculationType; @ExtendWith(MockitoExtension.class) -public class BatchCalculatorServiceTest { + class BatchCalculatorServiceTest { @Mock CalculatorService calculatorService; @@ -35,7 +35,7 @@ public class BatchCalculatorServiceTest { BatchCalculatorService batchCalculatorServiceNoMock; @BeforeEach - public void init() { + void init() { batchCalculatorService = new BatchCalculatorServiceImpl(calculatorService); batchCalculatorServiceNoMock = new BatchCalculatorServiceImpl( @@ -44,7 +44,7 @@ public void init() { } @Test - public void givenOperationsList_whenbatchCalculate_thenReturnsCorrectAnswerList() + void givenOperationsList_whenbatchCalculate_thenReturnsCorrectAnswerList() throws IOException, URISyntaxException { // GIVEN final Stream operations = Arrays.asList("2 + 2", "5 - 4", "6 x 8", "9 / 3").stream(); @@ -57,7 +57,7 @@ public void givenOperationsList_whenbatchCalculate_thenReturnsCorrectAnswerList( } @Test - public void givenOperationsList_whenbatchCalculate_thenCallsServiceWithCorrectArguments() { + void givenOperationsList_whenbatchCalculate_thenCallsServiceWithCorrectArguments() { // GIVEN final Stream operations = Arrays.asList("2 + 2", "5 - 4", "6 x 8", "9 / 3").stream(); final ArgumentCaptor calculationModelCaptor = ArgumentCaptor.forClass(CalculationModel.class); @@ -79,7 +79,7 @@ public void givenOperationsList_whenbatchCalculate_thenCallsServiceWithCorrectAr } @Test - public void givenOperationsList_whenbatchCalculate_thenCallsServiceAndReturnsAnswer() { + void givenOperationsList_whenbatchCalculate_thenCallsServiceAndReturnsAnswer() { // GIVEN final Stream operations = Arrays.asList("2 + 2", "5 - 4", "6 x 8", "9 / 3").stream(); when(calculatorService.calculate(any(CalculationModel.class))) @@ -113,7 +113,7 @@ public void givenOperationsList_whenbatchCalculate_thenCallsServiceAndReturnsAns } @Test - public void givenOperationsList_whenbatchCalculate_thenCallsServiceAndReturnsAnswer2() { + void givenOperationsList_whenbatchCalculate_thenCallsServiceAndReturnsAnswer2() { // GIVEN final Stream operations = Arrays.asList("2 + 2", "5 - 4", "6 x 8", "9 / 3").stream(); when(calculatorService.calculate(any(CalculationModel.class))) diff --git a/src/test/java/tech/zerofiltre/testing/calcul/service/CalculatorServiceIT.java b/src/test/java/tech/zerofiltre/testing/calcul/service/CalculatorServiceIT.java index 18f204b..dc6a2c3 100644 --- a/src/test/java/tech/zerofiltre/testing/calcul/service/CalculatorServiceIT.java +++ b/src/test/java/tech/zerofiltre/testing/calcul/service/CalculatorServiceIT.java @@ -3,29 +3,28 @@ import static org.assertj.core.api.Assertions.assertThat; import org.junit.jupiter.api.Test; - import tech.zerofiltre.testing.calcul.domain.Calculator; import tech.zerofiltre.testing.calcul.domain.model.CalculationModel; import tech.zerofiltre.testing.calcul.domain.model.CalculationType; -public class CalculatorServiceIT { +class CalculatorServiceIT { - // Mettre en place des objets réels non mockés - private final Calculator calculator = new Calculator(); - private final SolutionFormatter formatter = new SolutionFormatterImpl(); + // Mettre en place des objets réels non mockés + private final Calculator calculator = new Calculator(); + private final SolutionFormatter formatter = new SolutionFormatterImpl(); - // Initialiser la classe à tester - private final CalculatorService underTest = new CalculatorServiceImpl(calculator, formatter); + // Initialiser la classe à tester + private final CalculatorService underTest = new CalculatorServiceImpl(calculator, formatter); - @Test - public void calculatorService_shouldCalculateASolution_whenGivenACalculationModel() { - // GIVEN - final CalculationModel calculation = new CalculationModel(CalculationType.ADDITION, - 100, 101); - // WHEN - final CalculationModel result = underTest.calculate(calculation); + @Test + void calculatorService_shouldCalculateASolution_whenGivenACalculationModel() { + // GIVEN + final CalculationModel calculation = new CalculationModel(CalculationType.ADDITION, + 100, 101); + // WHEN + final CalculationModel result = underTest.calculate(calculation); - // THEN - assertThat(result.getSolution()).isEqualTo(201); - } + // THEN + assertThat(result.getSolution()).isEqualTo(201); + } } diff --git a/src/test/java/tech/zerofiltre/testing/calcul/service/CalculatorServiceTest.java b/src/test/java/tech/zerofiltre/testing/calcul/service/CalculatorServiceTest.java index 8c18448..11334d6 100644 --- a/src/test/java/tech/zerofiltre/testing/calcul/service/CalculatorServiceTest.java +++ b/src/test/java/tech/zerofiltre/testing/calcul/service/CalculatorServiceTest.java @@ -9,133 +9,131 @@ import static org.mockito.Mockito.when; import java.util.Random; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - import tech.zerofiltre.testing.calcul.domain.Calculator; import tech.zerofiltre.testing.calcul.domain.model.CalculationModel; import tech.zerofiltre.testing.calcul.domain.model.CalculationType; @ExtendWith(MockitoExtension.class) -public class CalculatorServiceTest { - - @Mock - Calculator calculator; - - @Mock - SolutionFormatter solutionFormatter; - - CalculatorService classUnderTest; - - @BeforeEach - public void init() { - classUnderTest = new CalculatorServiceImpl(calculator, solutionFormatter); - } - - @Test - public void calculate_shouldUseCalculator_forAddition() { - // GIVEN - when(calculator.add(1, 2)).thenReturn(3); - - // WHEN - final int result = classUnderTest.calculate( - new CalculationModel(CalculationType.ADDITION, 1, 2)).getSolution(); - - // THEN - verify(calculator).add(1, 2); - assertThat(result).isEqualTo(3); - } - - @Test - public void calculate_shouldUseCalculator_forSubstraction() { - // GIVEN - when(calculator.sub(3, 2)).thenReturn(1); - - // WHEN - final int result = classUnderTest.calculate( - new CalculationModel(CalculationType.SUBTRACTION, 3, 2)) - .getSolution(); - - // THEN - verify(calculator).sub(3, 2); - assertThat(result).isEqualTo(1); - } - - @Test - public void calculate_shouldUseCalculator_forMultiplication() { - // GIVEN - when(calculator.multiply(-3, 2)).thenReturn(-6); - - // WHEN - final int result = classUnderTest.calculate( - new CalculationModel(CalculationType.MULTIPLICATION, -3, 2)) - .getSolution(); - - // THEN - verify(calculator).multiply(-3, 2); - assertThat(result).isEqualTo(-6); - } - - @Test - public void calculate_shouldUseCalculator_forDivision() { - // GIVEN - when(calculator.divide(6, 3)).thenReturn(2); - - // WHEN - final int result = classUnderTest.calculate( - new CalculationModel(CalculationType.DIVISION, 6, 3)) - .getSolution(); - - // THEN - verify(calculator).divide(6, 3); - assertThat(result).isEqualTo(2); - } - - @Test - public void calculate_shouldUseCalculator_forAnyAddition() { - // GIVEN - final Random r = new Random(); - when(calculator.add(any(Integer.class), any(Integer.class))).thenReturn(3); - - // WHEN - final int result = classUnderTest.calculate( - new CalculationModel(CalculationType.ADDITION, r.nextInt(), r.nextInt())).getSolution(); - - // THEN - verify(calculator, times(1)).add(any(Integer.class), any(Integer.class)); - verify(calculator, never()).sub(any(Integer.class), any(Integer.class)); - assertThat(result).isEqualTo(3); - } - - @Test - public void calculate_shouldThrowIllegalArgumentAxception_forADivisionBy0() { - // GIVEN - when(calculator.divide(1, 0)).thenThrow(new ArithmeticException()); - - // WHEN - assertThrows(IllegalArgumentException.class, () -> classUnderTest.calculate( - new CalculationModel(CalculationType.DIVISION, 1, 0))); - - // THEN - verify(calculator, times(1)).divide(1, 0); - } - - @Test - public void calculate_shouldFormatSolution_forAnAddition() { - // GIVEN - when(calculator.add(10000, 3000)).thenReturn(13000); - when(solutionFormatter.format(13000)).thenReturn("13 000"); - - // WHEN - final String formattedResult = classUnderTest.calculate( - new CalculationModel(CalculationType.ADDITION, 10000, 3000)).getFormattedSolution(); - - // THEN - assertThat(formattedResult).isEqualTo("13 000"); - } +class CalculatorServiceTest { + + @Mock + Calculator calculator; + + @Mock + SolutionFormatter solutionFormatter; + + CalculatorService classUnderTest; + + @BeforeEach + void init() { + classUnderTest = new CalculatorServiceImpl(calculator, solutionFormatter); + } + + @Test + void calculate_shouldUseCalculator_forAddition() { + // GIVEN + when(calculator.add(1, 2)).thenReturn(3); + + // WHEN + final int result = classUnderTest.calculate( + new CalculationModel(CalculationType.ADDITION, 1, 2)).getSolution(); + + // THEN + verify(calculator).add(1, 2); + assertThat(result).isEqualTo(3); + } + + @Test + void calculate_shouldUseCalculator_forSubstraction() { + // GIVEN + when(calculator.sub(3, 2)).thenReturn(1); + + // WHEN + final int result = classUnderTest.calculate( + new CalculationModel(CalculationType.SUBTRACTION, 3, 2)) + .getSolution(); + + // THEN + verify(calculator).sub(3, 2); + assertThat(result).isEqualTo(1); + } + + @Test + void calculate_shouldUseCalculator_forMultiplication() { + // GIVEN + when(calculator.multiply(-3, 2)).thenReturn(-6); + + // WHEN + final int result = classUnderTest.calculate( + new CalculationModel(CalculationType.MULTIPLICATION, -3, 2)) + .getSolution(); + + // THEN + verify(calculator).multiply(-3, 2); + assertThat(result).isEqualTo(-6); + } + + @Test + void calculate_shouldUseCalculator_forDivision() { + // GIVEN + when(calculator.divide(6, 3)).thenReturn(2); + + // WHEN + final int result = classUnderTest.calculate( + new CalculationModel(CalculationType.DIVISION, 6, 3)) + .getSolution(); + + // THEN + verify(calculator).divide(6, 3); + assertThat(result).isEqualTo(2); + } + + @Test + void calculate_shouldUseCalculator_forAnyAddition() { + // GIVEN + final Random r = new Random(); + when(calculator.add(any(Integer.class), any(Integer.class))).thenReturn(3); + + // WHEN + final int result = classUnderTest.calculate( + new CalculationModel(CalculationType.ADDITION, r.nextInt(), r.nextInt())).getSolution(); + + // THEN + verify(calculator, times(1)).add(any(Integer.class), any(Integer.class)); + verify(calculator, never()).sub(any(Integer.class), any(Integer.class)); + assertThat(result).isEqualTo(3); + } + + @Test + void calculate_shouldThrowIllegalArgumentAxception_forADivisionBy0() { + // GIVEN + when(calculator.divide(1, 0)).thenThrow(new ArithmeticException()); + + // WHEN + CalculationModel calculationModel = new CalculationModel(CalculationType.DIVISION, 1, 0); + assertThrows(IllegalArgumentException.class, () -> classUnderTest.calculate(calculationModel)); + + // THEN + verify(calculator, times(1)).divide(1, 0); + } + + @Test + void calculate_shouldFormatSolution_forAnAddition() { + // GIVEN + when(calculator.add(10000, 3000)).thenReturn(13000); + when(solutionFormatter.format(13000)).thenReturn("13 000"); + + // WHEN + final String formattedResult = classUnderTest.calculate( + new CalculationModel(CalculationType.ADDITION, 10000, 3000)).getFormattedSolution(); + + // THEN + assertThat(formattedResult).isEqualTo("13 000"); + } } diff --git a/src/test/java/tech/zerofiltre/testing/calcul/service/SolutionFormatterTest.java b/src/test/java/tech/zerofiltre/testing/calcul/service/SolutionFormatterTest.java index 15f0006..3e3ab4a 100644 --- a/src/test/java/tech/zerofiltre/testing/calcul/service/SolutionFormatterTest.java +++ b/src/test/java/tech/zerofiltre/testing/calcul/service/SolutionFormatterTest.java @@ -5,25 +5,25 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class SolutionFormatterTest { +class SolutionFormatterTest { - private SolutionFormatter solutionFormatter; + private SolutionFormatter solutionFormatter; - @BeforeEach - public void initFormatter() { - solutionFormatter = new SolutionFormatterImpl(); - } + @BeforeEach + void initFormatter() { + solutionFormatter = new SolutionFormatterImpl(); + } - @Test - public void format_shouldFormatAnyBigNumber() { - // GIVEN - final int number = 1234567890; + @Test + void format_shouldFormatAnyBigNumber() { + // GIVEN + final int number = 1234567890; - // WHEN - final String result = solutionFormatter.format(number); + // WHEN + final String result = solutionFormatter.format(number); - // THEN - assertThat(result).isEqualTo("1 234 567 890"); - } + // THEN + assertThat(result).isEqualTo("1 234 567 890"); + } }