diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..a9c0651f8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM tomcat:8.5.50-jdk8-openjdk + +ARG WAR_FILE +ARG CONTEXT + +COPY ${WAR_FILE} /usr/local/tomcat/webapps/${CONTEXT}.war \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 000000000..e93e5d483 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,91 @@ +pipeline{ + agent any + stages{ + stage('Build Backend'){ + steps{ + bat 'mvn clean package -DskipTests=true' + } + } + stage('Unit Tests'){ + steps{ + bat 'mvn test' + } + } + //stage('Sonar Analysis'){ + // environment{ + // scannerHome = tool 'SONAR_SCANNER' + // } + // steps{ + // withSonarQubeEnv('SONAR_LOCAL') + + // bat "${scannerHome}/bin/sonar-scanner -e -Dsonar.projectKey=DeployBack -Dsonar.host.url=http://localhost:9000 -Dsonar.login=cf6826d57f1e453e08ecbd6cf862472061f66 -Dsonar.java.binaries=target -Dsonar.coverage.exclusions=**/.mvn/**,**/src/test/**,**/model/**,**Application.java" + // } + //} + //stage('Quality Gate'){ + // steps{ + // sleep(20) + // timeout(time:1,unit:'MINUTES'){ + // waitForQualityGate abortPipeline: true + // + // } + //} + stage('Deploy Backend'){ + steps{ + deploy adapters: [tomcat8(credentialsId: 'TomcatAdmin', path: '', url: 'http://localhost:8001')], contextPath: 'tasks-backend', war: 'target/tasks-backend.war' + } + } + stage('API Test'){ + steps{ + dir('api-test'){ + git credentialsId: '23956211-31cd-44a1-80c5-5d627a0f1d03', url: 'https://github.com/HenriqueGalli/tasks-api-test' + bat 'mvn test' + } + } + } + stage('Deploy Frontend'){ + steps{ + dir('frontend'){ + git credentialsId: '23956211-31cd-44a1-80c5-5d627a0f1d03', url: 'https://github.com/HenriqueGalli/tasks-frontend' + bat 'mvn clean package' + deploy adapters: [tomcat8(credentialsId: 'TomcatAdmin', path: '', url: 'http://localhost:8001')], contextPath: 'tasks', war: 'target/tasks.war' + } + + } + } + stage('Functional Tests'){ + steps{ + dir('functional-test'){ + git credentialsId: '23956211-31cd-44a1-80c5-5d627a0f1d03', url: 'https://github.com/HenriqueGalli/functional-test' + bat 'mvn test' + } + } + } + stage('Deploy prod'){ + steps{ + bat 'docker-compose build' + bat 'docker-compose up -d' + } + } + stage('Health Check'){ + steps{ + sleep(10) + dir('functional-test'){ + bat 'mvn verify -Dskip.surefire.tests' + } + } + } + } + post{ + always{ + junit allowEmptyResults: true, testResults: 'target/surefire-reports/*.xml,api-test/target/surefire-reports/*.xml,functional-test/target/surefire-reports/*.xml,functional-test/target/failsafe-reports/*.xml' + archiveArtifacts artifacts: 'target/tasks-backend.war, frontend/target/tasks.war', onlyIfSuccessful:true + } + unsuccessful{ + emailext attachLog: true, body: 'See the attached log', subject: 'Build Failed', to: 'henrique.galli@atomicsolutions.com.br' + } + fixed{ + emailext attachLog: true, body: 'See the attached log below', subject: 'Build is fixed', to: 'henrique.galli@atomicsolutions.com.br' + } + } +} + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..6bf30e27e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,62 @@ +version: "3" +services: + db: + container_name: pg-prod + image: postgres:9.6 + networks: + - prod_net_back + environment: + - POSTGRES_PASSWORD=passwd + - POSTGRES_DB=tasks + volumes: + - prod_postgresql:/var/lib/postgresql + - prod_postgresql_data:/var/lib/postgresql/data + + backend: + container_name: backend-prod + image: back_prod:build_${BUILD_NUMBER} + build: + context: . + args: + - WAR_FILE=target/tasks-backend.war + - CONTEXT=tasks-backend + networks: + - prod_net_back + - prod_net_front + #ports: + # - 9998:8080 + environment: + - DATABASE_HOST=db + - DATABASE_PORT=5432 + - DATABASE_USER=postgres + - DATABASE_PASSWD=passwd + - DATABASE_UPDATE=none + depends_on: + - db + + frontend: + container_name: frontend-prod + image: front_prod:build_${BUILD_NUMBER} + build: + context: . + args: + - WAR_FILE=frontend/target/tasks.war + - CONTEXT=tasks + networks: + - prod_net_front + ports: + - 9999:8080 + environment: + - BACKEND_HOST=backend + - BACKEND_PORT=8080 + - APP_VERSION=build_${BUILD_NUMBER} + #depends_on: + # - backend + +networks: + prod_net_front: + prod_net_back: + +volumes: + prod_postgresql: + prod_postgresql_data: \ No newline at end of file diff --git a/src/main/java/br/ce/wcaquino/taskbackend/controller/TaskController.java b/src/main/java/br/ce/wcaquino/taskbackend/controller/TaskController.java index 2b9281372..eb45ce32e 100644 --- a/src/main/java/br/ce/wcaquino/taskbackend/controller/TaskController.java +++ b/src/main/java/br/ce/wcaquino/taskbackend/controller/TaskController.java @@ -2,13 +2,18 @@ import java.util.List; +import javax.net.ssl.HttpsURLConnection; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import br.ce.wcaquino.taskbackend.model.Task; @@ -42,4 +47,11 @@ public ResponseEntity save(@RequestBody Task todo) throws ValidationExcept Task saved = todoRepo.save(todo); return new ResponseEntity(saved, HttpStatus.CREATED); } + + @DeleteMapping(value = "/{id}") + @ResponseStatus(code=HttpStatus.NO_CONTENT) + public void delete(@PathVariable Long id) { + todoRepo.deleteById(id); + } + } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 176b13ed1..f6b266839 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -5,7 +5,7 @@ spring.datasource.hikari.connectionTimeout=20000 spring.datasource.hikari.maximumPoolSize=5 ## PostgreSQL -spring.datasource.url=jdbc:postgresql://${DATABASE_HOST:127.0.0.1}:${DATABASE_PORT:5433}/tasks +spring.datasource.url=jdbc:postgresql://${DATABASE_HOST:192.168.99.100}:${DATABASE_PORT:5433}/tasks spring.datasource.username=${DATABASE_USER:postgres} spring.datasource.password=${DATABASE_PASSWD:password} diff --git a/src/test/java/br/ce/wcaquino/taskbackend/controller/TaskControllerTest.java b/src/test/java/br/ce/wcaquino/taskbackend/controller/TaskControllerTest.java new file mode 100644 index 000000000..b7afb3e0e --- /dev/null +++ b/src/test/java/br/ce/wcaquino/taskbackend/controller/TaskControllerTest.java @@ -0,0 +1,80 @@ +package br.ce.wcaquino.taskbackend.controller; + +import java.time.LocalDate; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import br.ce.wcaquino.taskbackend.model.Task; +import br.ce.wcaquino.taskbackend.repo.TaskRepo; +import br.ce.wcaquino.taskbackend.utils.ValidationException; + +public class TaskControllerTest { + + @Mock + private TaskRepo taskRepo; + + @InjectMocks + private TaskController controller; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void naoDeveSalvarTarefaSemDescricao() { + Task todo = new Task(); + todo.setDueDate(LocalDate.now()); + + try { + controller.save(todo); + Assert.fail("Nao deveria chegar nesse ponto"); + } catch (ValidationException e) { + Assert.assertEquals("Fill the task description", e.getMessage()); + } + } + + @Test + public void naoDeveSalvarTarefaSemData() { + Task todo = new Task(); + todo.setTask("Descricao"); + + try { + controller.save(todo); + Assert.fail("Nao deveria chegar nesse ponto"); + } catch (ValidationException e) { + Assert.assertEquals("Fill the due date", e.getMessage()); + } + } + + @Test + public void naoDeveSalvarTarefaComDataPassada() { + Task todo = new Task(); + todo.setTask("Descricao"); + todo.setDueDate(LocalDate.of(2010,01,01)); + + try { + controller.save(todo); + Assert.fail("Nao deveria chegar nesse ponto"); + } catch (ValidationException e) { + Assert.assertEquals("Due date must not be in past", e.getMessage()); + } + } + + @Test + public void deveSalvarTarefaComSucesso() throws ValidationException { + Task todo = new Task(); + todo.setTask("Descricao"); + todo.setDueDate(LocalDate.now()); + controller.save(todo); + + Mockito.verify(taskRepo).save(todo); + + } +} diff --git a/src/test/java/br/ce/wcaquino/taskbackend/utils/DateUtilsTest.java b/src/test/java/br/ce/wcaquino/taskbackend/utils/DateUtilsTest.java new file mode 100644 index 000000000..dd0e14972 --- /dev/null +++ b/src/test/java/br/ce/wcaquino/taskbackend/utils/DateUtilsTest.java @@ -0,0 +1,30 @@ +package br.ce.wcaquino.taskbackend.utils; + +import java.time.LocalDate; + +import org.junit.Assert; +import org.junit.Test; + +public class DateUtilsTest { + + @Test + public void deveRetornarTrueParaDatasFuturas() { + LocalDate date = LocalDate.of(2030, 01, 01); + Assert.assertTrue(DateUtils.isEqualOrFutureDate(date)); + + } + + @Test + public void deveRetornarFalseParaDatasPassadas() { + LocalDate date = LocalDate.of(2010, 01, 01); + Assert.assertFalse(DateUtils.isEqualOrFutureDate(date)); + + } + + @Test + public void deveRetornarTrueParaDataAtual() { + LocalDate date = LocalDate.now(); + Assert.assertTrue(DateUtils.isEqualOrFutureDate(date)); + + } +}