From 23495b9810a66eb37eb7aa4746041bfb1aaf3651 Mon Sep 17 00:00:00 2001 From: Cyril Dubuisson Date: Mon, 6 Apr 2020 11:50:04 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20:=20migrate=20backend=20fo?= =?UTF-8?q?r=20job?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/codeka/gaia/runner/StackRunner.java | 34 +-- .../stacks/controller/JobRestController.java | 79 ------ .../stacks/controller/JobRestController.kt | 67 +++++ .../stacks/controller/StackController.java | 80 ------ .../controller/StackRestController.java | 54 +++- .../gaia/stacks/repository/JobRepository.java | 17 -- .../gaia/stacks/repository/JobRepository.kt | 15 + .../codeka/gaia/runner/StackRunnerTest.java | 21 +- .../controller/JobRestControllerTest.java | 213 --------------- .../controller/JobRestControllerTest.kt | 256 ++++++++++++++++++ .../controller/StackControllerTest.java | 148 ---------- .../controller/StackRestControllerTest.java | 68 ++++- 12 files changed, 456 insertions(+), 596 deletions(-) delete mode 100644 src/main/java/io/codeka/gaia/stacks/controller/JobRestController.java create mode 100644 src/main/java/io/codeka/gaia/stacks/controller/JobRestController.kt delete mode 100644 src/main/java/io/codeka/gaia/stacks/controller/StackController.java delete mode 100644 src/main/java/io/codeka/gaia/stacks/repository/JobRepository.java create mode 100644 src/main/java/io/codeka/gaia/stacks/repository/JobRepository.kt delete mode 100644 src/test/java/io/codeka/gaia/stacks/controller/JobRestControllerTest.java create mode 100644 src/test/java/io/codeka/gaia/stacks/controller/JobRestControllerTest.kt delete mode 100644 src/test/java/io/codeka/gaia/stacks/controller/StackControllerTest.java diff --git a/src/main/java/io/codeka/gaia/runner/StackRunner.java b/src/main/java/io/codeka/gaia/runner/StackRunner.java index 06d2aada8..2ab3191f2 100644 --- a/src/main/java/io/codeka/gaia/runner/StackRunner.java +++ b/src/main/java/io/codeka/gaia/runner/StackRunner.java @@ -15,6 +15,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.function.Consumer; import java.util.function.IntConsumer; import java.util.function.Supplier; @@ -119,20 +120,20 @@ private void treatJob(JobWorkflow jobWorkflow, Consumer jobActionFn @Async public void plan(JobWorkflow jobWorkflow, TerraformModule module, Stack stack) { treatJob( - jobWorkflow, - JobWorkflow::plan, - () -> managePlanScript(jobWorkflow.getJob(), stack, module), - result -> managePlanResult(result, jobWorkflow, stack) + jobWorkflow, + JobWorkflow::plan, + () -> managePlanScript(jobWorkflow.getJob(), stack, module), + result -> managePlanResult(result, jobWorkflow, stack) ); } @Async public void apply(JobWorkflow jobWorkflow, TerraformModule module, Stack stack) { treatJob( - jobWorkflow, - JobWorkflow::apply, - () -> manageApplyScript(jobWorkflow.getJob(), stack, module), - result -> manageApplyResult(result, jobWorkflow, stack) + jobWorkflow, + JobWorkflow::apply, + () -> manageApplyScript(jobWorkflow.getJob(), stack, module), + result -> manageApplyResult(result, jobWorkflow, stack) ); } @@ -140,20 +141,15 @@ public void apply(JobWorkflow jobWorkflow, TerraformModule module, Stack stack) public void retry(JobWorkflow jobWorkflow, TerraformModule module, Stack stack) { stepRepository.deleteByJobId(jobWorkflow.getJob().getId()); treatJob( - jobWorkflow, - JobWorkflow::retry, - () -> managePlanScript(jobWorkflow.getJob(), stack, module), - result -> managePlanResult(result, jobWorkflow, stack) + jobWorkflow, + JobWorkflow::retry, + () -> managePlanScript(jobWorkflow.getJob(), stack, module), + result -> managePlanResult(result, jobWorkflow, stack) ); } - public Job getJob(String jobId) { - if (this.jobs.containsKey(jobId)) { - // try in memory - return this.jobs.get(jobId); - } - // or find in repository - return this.jobRepository.findById(jobId).orElseThrow(() -> new RuntimeException("job not found")); + public Optional getJob(String jobId) { + return Optional.ofNullable(this.jobs.get(jobId)); } } diff --git a/src/main/java/io/codeka/gaia/stacks/controller/JobRestController.java b/src/main/java/io/codeka/gaia/stacks/controller/JobRestController.java deleted file mode 100644 index 4e5addf39..000000000 --- a/src/main/java/io/codeka/gaia/stacks/controller/JobRestController.java +++ /dev/null @@ -1,79 +0,0 @@ -package io.codeka.gaia.stacks.controller; - -import io.codeka.gaia.modules.repository.TerraformModuleRepository; -import io.codeka.gaia.runner.StackRunner; -import io.codeka.gaia.stacks.bo.Job; -import io.codeka.gaia.stacks.bo.StepType; -import io.codeka.gaia.stacks.repository.JobRepository; -import io.codeka.gaia.stacks.repository.StackRepository; -import io.codeka.gaia.stacks.repository.StepRepository; -import io.codeka.gaia.stacks.workflow.JobWorkflow; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RestController -@RequestMapping("/api/jobs") -public class JobRestController { - - private JobRepository jobRepository; - private StackRepository stackRepository; - private TerraformModuleRepository moduleRepository; - private StackRunner stackRunner; - private StepRepository stepRepository; - - @Autowired - public JobRestController(JobRepository jobRepository, StackRepository stackRepository, - TerraformModuleRepository moduleRepository, StackRunner stackRunner, - StepRepository stepRepository) { - this.jobRepository = jobRepository; - this.stackRepository = stackRepository; - this.moduleRepository = moduleRepository; - this.stackRunner = stackRunner; - this.stepRepository = stepRepository; - } - - @GetMapping(params = "stackId") - public List jobs(@RequestParam String stackId) { - return this.jobRepository.findAllByStackId(stackId); - } - - @GetMapping("/{id}") - public Job job(@PathVariable String id) { - return this.jobRepository.findById(id).orElseThrow(JobNotFoundException::new); - } - - @PostMapping("/{id}/{stepType}") - public void planOrApplyJob(@PathVariable String id, @PathVariable StepType stepType) { - var job = this.jobRepository.findById(id).orElseThrow(JobNotFoundException::new); - var stack = this.stackRepository.findById(job.getStackId()).orElseThrow(); - var module = this.moduleRepository.findById(stack.getModuleId()).orElseThrow(); - - if (StepType.PLAN == stepType) { - this.stackRunner.plan(new JobWorkflow(job), module, stack); - } else { - this.stackRunner.apply(new JobWorkflow(job), module, stack); - } - } - - @PostMapping("/{id}/retry") - public void retryJob(@PathVariable String id) { - var job = this.jobRepository.findById(id).orElseThrow(JobNotFoundException::new); - var stack = this.stackRepository.findById(job.getStackId()).orElseThrow(); - var module = this.moduleRepository.findById(stack.getModuleId()).orElseThrow(); - - this.stackRunner.retry(new JobWorkflow(job), module, stack); - } - - @DeleteMapping("/{id}") - public void deleteJob(@PathVariable String id) { - this.stepRepository.deleteByJobId(id); - this.jobRepository.deleteById(id); - } -} - -@ResponseStatus(HttpStatus.NOT_FOUND) -class JobNotFoundException extends RuntimeException { -} diff --git a/src/main/java/io/codeka/gaia/stacks/controller/JobRestController.kt b/src/main/java/io/codeka/gaia/stacks/controller/JobRestController.kt new file mode 100644 index 000000000..d2e0a9b0c --- /dev/null +++ b/src/main/java/io/codeka/gaia/stacks/controller/JobRestController.kt @@ -0,0 +1,67 @@ +package io.codeka.gaia.stacks.controller + +import io.codeka.gaia.modules.repository.TerraformModuleRepository +import io.codeka.gaia.runner.StackRunner +import io.codeka.gaia.stacks.bo.Job +import io.codeka.gaia.stacks.repository.JobRepository +import io.codeka.gaia.stacks.repository.StackRepository +import io.codeka.gaia.stacks.repository.StepRepository +import io.codeka.gaia.stacks.workflow.JobWorkflow +import org.springframework.http.HttpStatus +import org.springframework.web.bind.annotation.* + +@RestController +@RequestMapping("/api/jobs") +class JobRestController( + private val jobRepository: JobRepository, + private val stackRepository: StackRepository, + private val moduleRepository: TerraformModuleRepository, + private val stackRunner: StackRunner, + private val stepRepository: StepRepository) { + + @GetMapping(params = ["stackId"]) + fun jobs(@RequestParam stackId: String) = jobRepository.findAllByStackId(stackId) + + @GetMapping("/{id}") + fun job(@PathVariable id: String): Job { + val runningJob = stackRunner.getJob(id); + if (runningJob.isPresent) { + return runningJob.get(); + } + return jobRepository.findById(id).orElseThrow { JobNotFoundException() } + } + + @PostMapping("/{id}/plan") + fun plan(@PathVariable id: String) { + val job = jobRepository.findById(id).orElseThrow { JobNotFoundException() } + val stack = stackRepository.findById(job.stackId).orElseThrow() + val module = moduleRepository.findById(stack.moduleId).orElseThrow() + stackRunner.plan(JobWorkflow(job), module, stack) + } + + @PostMapping("/{id}/apply") + fun apply(@PathVariable id: String) { + val job = jobRepository.findById(id).orElseThrow { JobNotFoundException() } + val stack = stackRepository.findById(job.stackId).orElseThrow() + val module = moduleRepository.findById(stack.moduleId).orElseThrow() + stackRunner.apply(JobWorkflow(job), module, stack) + } + + @PostMapping("/{id}/retry") + fun retry(@PathVariable id: String) { + val job = jobRepository.findById(id).orElseThrow { JobNotFoundException() } + val stack = stackRepository.findById(job.stackId).orElseThrow() + val module = moduleRepository.findById(stack.moduleId).orElseThrow() + stackRunner.retry(JobWorkflow(job), module, stack) + } + + @DeleteMapping("/{id}") + fun delete(@PathVariable id: String) { + stepRepository.deleteByJobId(id) + jobRepository.deleteById(id) + } + +} + +@ResponseStatus(HttpStatus.NOT_FOUND) +internal class JobNotFoundException : RuntimeException() diff --git a/src/main/java/io/codeka/gaia/stacks/controller/StackController.java b/src/main/java/io/codeka/gaia/stacks/controller/StackController.java deleted file mode 100644 index 0a957a68d..000000000 --- a/src/main/java/io/codeka/gaia/stacks/controller/StackController.java +++ /dev/null @@ -1,80 +0,0 @@ -package io.codeka.gaia.stacks.controller; - -import io.codeka.gaia.modules.repository.TerraformModuleRepository; -import io.codeka.gaia.runner.StackRunner; -import io.codeka.gaia.stacks.bo.Job; -import io.codeka.gaia.stacks.bo.JobType; -import io.codeka.gaia.stacks.repository.JobRepository; -import io.codeka.gaia.stacks.repository.StackRepository; -import io.codeka.gaia.teams.User; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.ResponseBody; - -@Controller -public class StackController { - - private StackRepository stackRepository; - - private StackRunner stackRunner; - - private TerraformModuleRepository terraformModuleRepository; - - private JobRepository jobRepository; - - @Autowired - public StackController(StackRepository stackRepository, StackRunner stackRunner, TerraformModuleRepository terraformModuleRepository, JobRepository jobRepository) { - this.stackRepository = stackRepository; - this.stackRunner = stackRunner; - this.terraformModuleRepository = terraformModuleRepository; - this.jobRepository = jobRepository; - } - -// @GetMapping("/stacks") - public String listStacks() { - return "stacks"; - } - -// @GetMapping("/stacks/{stackId}/{jobType}") - public String launchJob(@PathVariable String stackId, @PathVariable JobType jobType, Model model, User user) { - // get the stack - var stack = this.stackRepository.findById(stackId).orElseThrow(StackNotFoundException::new); - model.addAttribute("stackId", stackId); - - // get the module - var module = this.terraformModuleRepository.findById(stack.getModuleId()).orElseThrow(); - - // create a new job - var job = new Job(jobType, stackId, user); - job.setTerraformImage(module.getTerraformImage()); - jobRepository.save(job); - model.addAttribute("jobId", job.getId()); - - return "job"; - } - -// @GetMapping("/stacks/{stackId}/jobs/{jobId}") - public String viewJob(@PathVariable String stackId, @PathVariable String jobId, Model model) { - // checking if the stack exists - // TODO throw an exception (404) if not - if (stackRepository.existsById(stackId)) { - model.addAttribute("stackId", stackId); - } - if (jobRepository.existsById(jobId)) { - model.addAttribute("jobId", jobId); - } - - model.addAttribute("edition", true); - return "job"; - } - - @GetMapping("/api/stacks/{stackId}/jobs/{jobId}") - @ResponseBody - public Job getJob(@PathVariable String stackId, @PathVariable String jobId) { - return this.stackRunner.getJob(jobId); - } - -} diff --git a/src/main/java/io/codeka/gaia/stacks/controller/StackRestController.java b/src/main/java/io/codeka/gaia/stacks/controller/StackRestController.java index ad7d0153a..e817abfe5 100644 --- a/src/main/java/io/codeka/gaia/stacks/controller/StackRestController.java +++ b/src/main/java/io/codeka/gaia/stacks/controller/StackRestController.java @@ -1,6 +1,10 @@ package io.codeka.gaia.stacks.controller; +import io.codeka.gaia.modules.repository.TerraformModuleRepository; +import io.codeka.gaia.stacks.bo.Job; +import io.codeka.gaia.stacks.bo.JobType; import io.codeka.gaia.stacks.bo.Stack; +import io.codeka.gaia.stacks.repository.JobRepository; import io.codeka.gaia.stacks.repository.StackRepository; import io.codeka.gaia.stacks.service.StackCostCalculator; import io.codeka.gaia.teams.Team; @@ -12,6 +16,7 @@ import javax.validation.Valid; import java.time.LocalDateTime; import java.util.List; +import java.util.Map; import java.util.UUID; @RestController @@ -22,33 +27,40 @@ public class StackRestController { private StackCostCalculator stackCostCalculator; + private TerraformModuleRepository terraformModuleRepository; + + private JobRepository jobRepository; + @Autowired - public StackRestController(StackRepository stackRepository, StackCostCalculator stackCostCalculator) { + public StackRestController( + StackRepository stackRepository, + StackCostCalculator stackCostCalculator, + TerraformModuleRepository terraformModuleRepository, + JobRepository jobRepository) { this.stackRepository = stackRepository; this.stackCostCalculator = stackCostCalculator; + this.terraformModuleRepository = terraformModuleRepository; + this.jobRepository = jobRepository; } @GetMapping - public List listStacks(User user){ - if(user.isAdmin()){ + public List listStacks(User user) { + if (user.isAdmin()) { return stackRepository.findAll(); - } - else if(user.getTeam() != null){ + } else if (user.getTeam() != null) { return stackRepository.findByOwnerTeam(user.getTeam()); } return stackRepository.findByCreatedBy(user); } @GetMapping("/{id}") - public Stack getStack(@PathVariable String id, User user){ + public Stack getStack(@PathVariable String id, User user) { Stack stack; - if(user.isAdmin()){ + if (user.isAdmin()) { stack = stackRepository.findById(id).orElseThrow(StackNotFoundException::new); - } - else if(user.getTeam() != null){ + } else if (user.getTeam() != null) { stack = stackRepository.findByIdAndOwnerTeam(id, user.getTeam()).orElseThrow(StackNotFoundException::new); - } - else { + } else { stack = stackRepository.findById(id).orElseThrow(StackNotFoundException::new); } stack.setEstimatedRunningCost(stackCostCalculator.calculateRunningCostEstimation(stack)); @@ -56,7 +68,7 @@ else if(user.getTeam() != null){ } @PostMapping - public Stack save(@RequestBody @Valid Stack stack, Team userTeam, User user){ + public Stack save(@RequestBody @Valid Stack stack, Team userTeam, User user) { stack.setOwnerTeam(userTeam); stack.setId(UUID.randomUUID().toString()); stack.setCreatedBy(user); @@ -65,12 +77,28 @@ public Stack save(@RequestBody @Valid Stack stack, Team userTeam, User user){ } @PutMapping("/{id}") - public Stack update(@PathVariable String id, @RequestBody @Valid Stack stack, User user){ + public Stack update(@PathVariable String id, @RequestBody @Valid Stack stack, User user) { stack.setUpdatedBy(user); stack.setUpdatedAt(LocalDateTime.now()); return stackRepository.save(stack); } + @PostMapping("/{id}/{jobType}") + public Map launchJob(@PathVariable String id, @PathVariable JobType jobType, User user) { + // get the stack + var stack = this.stackRepository.findById(id).orElseThrow(StackNotFoundException::new); + + // get the module + var module = this.terraformModuleRepository.findById(stack.getModuleId()).orElseThrow(); + + // create a new job + var job = new Job(jobType, id, user); + job.setTerraformImage(module.getTerraformImage()); + jobRepository.save(job); + + return Map.of("jobId", job.getId()); + } + } @ResponseStatus(HttpStatus.NOT_FOUND) diff --git a/src/main/java/io/codeka/gaia/stacks/repository/JobRepository.java b/src/main/java/io/codeka/gaia/stacks/repository/JobRepository.java deleted file mode 100644 index cec6545f7..000000000 --- a/src/main/java/io/codeka/gaia/stacks/repository/JobRepository.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.codeka.gaia.stacks.repository; - -import io.codeka.gaia.stacks.bo.Job; -import org.springframework.data.mongodb.repository.MongoRepository; -import org.springframework.stereotype.Repository; - -import java.util.List; - -/** - * Repository for jobs - */ -@Repository -public interface JobRepository extends MongoRepository{ - - List findAllByStackId(String stackId); - -} diff --git a/src/main/java/io/codeka/gaia/stacks/repository/JobRepository.kt b/src/main/java/io/codeka/gaia/stacks/repository/JobRepository.kt new file mode 100644 index 000000000..5cb834f6c --- /dev/null +++ b/src/main/java/io/codeka/gaia/stacks/repository/JobRepository.kt @@ -0,0 +1,15 @@ +package io.codeka.gaia.stacks.repository + +import io.codeka.gaia.stacks.bo.Job +import org.springframework.data.mongodb.repository.MongoRepository +import org.springframework.stereotype.Repository + +/** + * Repository for jobs + */ +@Repository +interface JobRepository : MongoRepository { + + fun findAllByStackId(stackId: String): List + +} diff --git a/src/test/java/io/codeka/gaia/runner/StackRunnerTest.java b/src/test/java/io/codeka/gaia/runner/StackRunnerTest.java index 7797b2746..ded5ba86c 100644 --- a/src/test/java/io/codeka/gaia/runner/StackRunnerTest.java +++ b/src/test/java/io/codeka/gaia/runner/StackRunnerTest.java @@ -147,7 +147,7 @@ void plan_shouldNotUpdateStack_WhenThereIsADiffForNewStacks() { // then assertEquals(StackState.NEW, stack.getState()); - verifyZeroInteractions(stackRepository); + verifyNoInteractions(stackRepository); } @Test @@ -162,7 +162,7 @@ void plan_shouldNotUpdateStack_WhenThereIsADiffAndJobIsStop() { // then assertEquals(StackState.RUNNING, stack.getState()); - verifyZeroInteractions(stackRepository); + verifyNoInteractions(stackRepository); } @Test @@ -365,7 +365,7 @@ void retry_shouldNotUpdateStack_WhenThereIsADiffForNewStacks() { // then assertEquals(StackState.NEW, stack.getState()); - verifyZeroInteractions(stackRepository); + verifyNoInteractions(stackRepository); } @Test @@ -380,7 +380,7 @@ void retry_shouldNotUpdateStack_WhenThereIsADiffAndJobIsStop() { // then assertEquals(StackState.RUNNING, stack.getState()); - verifyZeroInteractions(stackRepository); + verifyNoInteractions(stackRepository); } @Test @@ -393,15 +393,4 @@ void retry_shouldSaveJobAndSteps() { verify(stepRepository, times(2)).saveAll(job.getSteps()); } - @Test - void getJob_shouldReturnJob_whenNotInMemory() { - // when - when(jobRepository.findById(anyString())).thenReturn(Optional.of(job)); - var result = stackRunner.getJob("test_jobId"); - - // then - assertEquals(job, result); - verify(jobRepository).findById("test_jobId"); - } - -} \ No newline at end of file +} diff --git a/src/test/java/io/codeka/gaia/stacks/controller/JobRestControllerTest.java b/src/test/java/io/codeka/gaia/stacks/controller/JobRestControllerTest.java deleted file mode 100644 index 7ea9ed166..000000000 --- a/src/test/java/io/codeka/gaia/stacks/controller/JobRestControllerTest.java +++ /dev/null @@ -1,213 +0,0 @@ -package io.codeka.gaia.stacks.controller; - -import io.codeka.gaia.modules.bo.TerraformModule; -import io.codeka.gaia.modules.repository.TerraformModuleRepository; -import io.codeka.gaia.runner.StackRunner; -import io.codeka.gaia.stacks.bo.Job; -import io.codeka.gaia.stacks.bo.Stack; -import io.codeka.gaia.stacks.bo.StepType; -import io.codeka.gaia.stacks.repository.JobRepository; -import io.codeka.gaia.stacks.repository.StackRepository; -import io.codeka.gaia.stacks.repository.StepRepository; -import io.codeka.gaia.stacks.workflow.JobWorkflow; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.util.NoSuchElementException; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class JobRestControllerTest { - - @Mock - private JobRepository jobRepository; - - @Mock - private StepRepository stepRepository; - - @Mock - private StackRepository stackRepository; - - @Mock - private TerraformModuleRepository moduleRepository; - - @Mock - private StackRunner stackRunner; - - @InjectMocks - private JobRestController controller; - - @Test - void jobs_shouldReturnAllJobs_forStackId() { - // when - controller.jobs("stackId"); - - // then - verify(jobRepository).findAllByStackId("stackId"); - } - - @Test - void job_shouldReturnASpecificJob() { - // given - var job = mock(Job.class); - when(jobRepository.findById("12")).thenReturn(Optional.of(job)); - - // when - controller.job("12"); - - // then - verify(jobRepository).findById("12"); - } - - @Test - void job_shouldThrowJobNotFoundException_forNonExistingJobs() { - // given - when(jobRepository.findById("12")).thenReturn(Optional.empty()); - - // when - assertThrows(JobNotFoundException.class, () -> controller.job("12")); - - // then - verify(jobRepository).findById("12"); - } - - @Test - void planOrApplyJob_shouldThrowJobNotFoundException_forNonExistingJob() { - // when - when(jobRepository.findById(any())).thenReturn(Optional.empty()); - - // then - assertThrows(JobNotFoundException.class, () -> controller.planOrApplyJob("test_jobId", StepType.PLAN)); - } - - @Test - void planOrApplyJob_shouldThrowException_forNonExistingStack() { - // when - when(jobRepository.findById(any())).thenReturn(Optional.of(new Job())); - when(stackRepository.findById(any())).thenReturn(Optional.empty()); - - // then - assertThrows(NoSuchElementException.class, () -> controller.planOrApplyJob("test_jobId", StepType.PLAN)); - } - - @Test - void planOrApplyJob_shouldThrowException_forNonExistingModule() { - // when - when(jobRepository.findById(any())).thenReturn(Optional.of(new Job())); - when(stackRepository.findById(any())).thenReturn(Optional.of(new Stack())); - when(moduleRepository.findById(any())).thenReturn(Optional.empty()); - - // then - assertThrows(NoSuchElementException.class, () -> controller.planOrApplyJob("test_jobId", StepType.PLAN)); - } - - @Test - void planOrApplyJob_shouldPlan_whenStepTypeIsPlan() { - // given - var job = new Job(); - var stack = new Stack(); - var module = new TerraformModule(); - - // when - when(jobRepository.findById(any())).thenReturn(Optional.of(job)); - when(stackRepository.findById(any())).thenReturn(Optional.of(stack)); - when(moduleRepository.findById(any())).thenReturn(Optional.of(module)); - controller.planOrApplyJob("test_jobId", StepType.PLAN); - - // then - var captor = ArgumentCaptor.forClass(JobWorkflow.class); - verify(stackRunner).plan(captor.capture(), eq(module), eq(stack)); - assertThat(captor.getValue()).isNotNull(); - assertThat(captor.getValue().getJob()).isNotNull().isEqualTo(job); - } - - @Test - void planOrApplyJob_shouldApply_whenStepTypeIsApply() { - // given - var job = new Job(); - var stack = new Stack(); - var module = new TerraformModule(); - - // when - when(jobRepository.findById(any())).thenReturn(Optional.of(job)); - when(stackRepository.findById(any())).thenReturn(Optional.of(stack)); - when(moduleRepository.findById(any())).thenReturn(Optional.of(module)); - controller.planOrApplyJob("test_jobId", StepType.APPLY); - - // then - var captor = ArgumentCaptor.forClass(JobWorkflow.class); - verify(stackRunner).apply(captor.capture(), eq(module), eq(stack)); - assertThat(captor.getValue()).isNotNull(); - assertThat(captor.getValue().getJob()).isNotNull().isEqualTo(job); - } - - @Test - void retryJob_shouldThrowJobNotFoundException_forNonExistingJob() { - // when - when(jobRepository.findById(any())).thenReturn(Optional.empty()); - - // then - assertThrows(JobNotFoundException.class, () -> controller.retryJob("test_jobId")); - } - - @Test - void retryJob_shouldThrowException_forNonExistingStack() { - // when - when(jobRepository.findById(any())).thenReturn(Optional.of(new Job())); - when(stackRepository.findById(any())).thenReturn(Optional.empty()); - - // then - assertThrows(NoSuchElementException.class, () -> controller.retryJob("test_jobId")); - } - - @Test - void retryJob_shouldThrowException_forNonExistingModule() { - // when - when(jobRepository.findById(any())).thenReturn(Optional.of(new Job())); - when(stackRepository.findById(any())).thenReturn(Optional.of(new Stack())); - when(moduleRepository.findById(any())).thenReturn(Optional.empty()); - - // then - assertThrows(NoSuchElementException.class, () -> controller.retryJob("test_jobId")); - } - - @Test - void retryJob_shouldRetry() { - // given - var job = new Job(); - var stack = new Stack(); - var module = new TerraformModule(); - - // when - when(jobRepository.findById(any())).thenReturn(Optional.of(job)); - when(stackRepository.findById(any())).thenReturn(Optional.of(stack)); - when(moduleRepository.findById(any())).thenReturn(Optional.of(module)); - controller.retryJob("test_jobId"); - - // then - var captor = ArgumentCaptor.forClass(JobWorkflow.class); - verify(stackRunner).retry(captor.capture(), eq(module), eq(stack)); - assertThat(captor.getValue()).isNotNull(); - assertThat(captor.getValue().getJob()).isNotNull().isEqualTo(job); - } - - @Test - void deleteJob_shouldDeleteJobAndSteps() { - // when - controller.deleteJob("test_jobId"); - - // then - verify(stepRepository, times(1)).deleteByJobId("test_jobId"); - verify(jobRepository, times(1)).deleteById("test_jobId"); - verifyNoMoreInteractions(stepRepository, jobRepository); - } - -} \ No newline at end of file diff --git a/src/test/java/io/codeka/gaia/stacks/controller/JobRestControllerTest.kt b/src/test/java/io/codeka/gaia/stacks/controller/JobRestControllerTest.kt new file mode 100644 index 000000000..d2d0ff188 --- /dev/null +++ b/src/test/java/io/codeka/gaia/stacks/controller/JobRestControllerTest.kt @@ -0,0 +1,256 @@ +package io.codeka.gaia.stacks.controller + +import io.codeka.gaia.modules.bo.TerraformModule +import io.codeka.gaia.modules.repository.TerraformModuleRepository +import io.codeka.gaia.runner.StackRunner +import io.codeka.gaia.stacks.bo.Job +import io.codeka.gaia.stacks.bo.Stack +import io.codeka.gaia.stacks.repository.JobRepository +import io.codeka.gaia.stacks.repository.StackRepository +import io.codeka.gaia.stacks.repository.StepRepository +import io.codeka.gaia.stacks.workflow.JobWorkflow +import io.codeka.gaia.test.whenever +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.ArgumentCaptor.forClass +import org.mockito.InjectMocks +import org.mockito.Mock +import org.mockito.Mockito.* +import org.mockito.junit.jupiter.MockitoExtension +import java.util.* +import java.util.Optional.empty +import java.util.Optional.of + +@ExtendWith(MockitoExtension::class) +class JobRestControllerTest { + + @Mock + lateinit var jobRepository: JobRepository + + @Mock + lateinit var stepRepository: StepRepository + + @Mock + lateinit var stackRepository: StackRepository + + @Mock + lateinit var moduleRepository: TerraformModuleRepository + + @Mock + lateinit var stackRunner: StackRunner + + @InjectMocks + lateinit var controller: JobRestController + + @Test + fun `jobs() should return all jobs for a stack id`() { + // when + controller.jobs("stackId") + + // then + verify(jobRepository).findAllByStackId("stackId") + } + + @Test + fun `job() should return the running job if exists`() { + // given + val job = mock(Job::class.java) + + // when + whenever(stackRunner.getJob(any())).thenReturn(of(job)) + controller.job("12") + + // then + verify(stackRunner).getJob("12") + verifyNoInteractions(jobRepository) + } + + @Test + fun `job() should return the saved job if no running`() { + // given + val job = mock(Job::class.java) + + // when + whenever(stackRunner.getJob(any())).thenReturn(empty()) + whenever(jobRepository.findById("12")).thenReturn(of(job)) + controller.job("12") + + // then + verify(stackRunner, times(1)).getJob("12") + verify(jobRepository).findById("12") + } + + @Test + fun `job() should throw an exception for non existing jobs`() { + // when + whenever(jobRepository.findById("12")).thenReturn(empty()) + + // then + assertThrows(JobNotFoundException::class.java) { controller.job("12") } + verify(jobRepository).findById("12") + } + + @Test + fun `plan() should throw an exception for non existing job`() { + // when + whenever(jobRepository.findById(any())).thenReturn(empty()) + + // then + assertThrows(JobNotFoundException::class.java) { controller.plan("test_jobId") } + } + + @Test + fun `plan() should throw an exception for non existing stack`() { + // when + whenever(jobRepository.findById(any())).thenReturn(of(Job())) + whenever(stackRepository.findById(any())).thenReturn(empty()) + + // then + assertThrows(NoSuchElementException::class.java) { controller.plan("test_jobId") } + } + + @Test + fun `plan() should throw an exception for non existing module`() { + // when + whenever(jobRepository.findById(any())).thenReturn(of(Job())) + whenever(stackRepository.findById(any())).thenReturn(of(Stack())) + whenever(moduleRepository.findById(any())).thenReturn(empty()) + + // then + assertThrows(NoSuchElementException::class.java) { controller.plan("test_jobId") } + } + + @Test + fun `plan() should plan a job`() { + // given + val job = Job() + val stack = Stack() + val module = TerraformModule() + + // when + whenever(jobRepository.findById(any())).thenReturn(of(job)) + whenever(stackRepository.findById(any())).thenReturn(of(stack)) + whenever(moduleRepository.findById(any())).thenReturn(of(module)) + controller.plan("test_jobId") + + // then + val captor = forClass(JobWorkflow::class.java) + verify(stackRunner).plan(captor.capture(), eq(module), eq(stack)) + assertThat(captor.value).isNotNull + assertThat(captor.value.job).isNotNull.isEqualTo(job) + } + + @Test + fun `apply() should throw an exception for non existing job`() { + // when + whenever(jobRepository.findById(any())).thenReturn(empty()) + + // then + assertThrows(JobNotFoundException::class.java) { controller.apply("test_jobId") } + } + + @Test + fun `apply() should throw an exception for non existing stack`() { + // when + whenever(jobRepository.findById(any())).thenReturn(of(Job())) + whenever(stackRepository.findById(any())).thenReturn(empty()) + + // then + assertThrows(NoSuchElementException::class.java) { controller.apply("test_jobId") } + } + + @Test + fun `apply() should throw an exception for non existing module`() { + // when + whenever(jobRepository.findById(any())).thenReturn(of(Job())) + whenever(stackRepository.findById(any())).thenReturn(of(Stack())) + whenever(moduleRepository.findById(any())).thenReturn(empty()) + + // then + assertThrows(NoSuchElementException::class.java) { controller.apply("test_jobId") } + } + + @Test + fun `apply() should apply a job`() { + // given + val job = Job() + val stack = Stack() + val module = TerraformModule() + + // when + whenever(jobRepository.findById(any())).thenReturn(of(job)) + whenever(stackRepository.findById(any())).thenReturn(of(stack)) + whenever(moduleRepository.findById(any())).thenReturn(of(module)) + controller.apply("test_jobId") + + // then + val captor = forClass(JobWorkflow::class.java) + verify(stackRunner).apply(captor.capture(), eq(module), eq(stack)) + assertThat(captor.value).isNotNull + assertThat(captor.value.job).isNotNull.isEqualTo(job) + } + + @Test + fun `retry() should throw an exception for non existing job`() { + // when + whenever(jobRepository.findById(any())).thenReturn(empty()) + + // then + assertThrows(JobNotFoundException::class.java) { controller.retry("test_jobId") } + } + + @Test + fun `retry() should throw an exception for non existing stack`() { + // when + whenever(jobRepository.findById(any())).thenReturn(of(Job())) + whenever(stackRepository.findById(any())).thenReturn(empty()) + + // then + assertThrows(NoSuchElementException::class.java) { controller.retry("test_jobId") } + } + + @Test + fun `retry() should throw an exception for non existing module`() { + // when + whenever(jobRepository.findById(any())).thenReturn(of(Job())) + whenever(stackRepository.findById(any())).thenReturn(of(Stack())) + whenever(moduleRepository.findById(any())).thenReturn(empty()) + + // then + assertThrows(NoSuchElementException::class.java) { controller.retry("test_jobId") } + } + + @Test + fun `retry() should retry a job`() { + // given + val job = Job() + val stack = Stack() + val module = TerraformModule() + + // when + whenever(jobRepository.findById(any())).thenReturn(of(job)) + whenever(stackRepository.findById(any())).thenReturn(of(stack)) + whenever(moduleRepository.findById(any())).thenReturn(of(module)) + controller.retry("test_jobId") + + // then + val captor = forClass(JobWorkflow::class.java) + verify(stackRunner).retry(captor.capture(), eq(module), eq(stack)) + assertThat(captor.value).isNotNull + assertThat(captor.value.job).isNotNull.isEqualTo(job) + } + + @Test + fun `delete() should delete a job and its steps`() { + // when + controller.delete("test_jobId") + + // then + verify(stepRepository, times(1)).deleteByJobId("test_jobId") + verify(jobRepository, times(1)).deleteById("test_jobId") + verifyNoMoreInteractions(stepRepository, jobRepository) + } + +} diff --git a/src/test/java/io/codeka/gaia/stacks/controller/StackControllerTest.java b/src/test/java/io/codeka/gaia/stacks/controller/StackControllerTest.java deleted file mode 100644 index 3a0f85296..000000000 --- a/src/test/java/io/codeka/gaia/stacks/controller/StackControllerTest.java +++ /dev/null @@ -1,148 +0,0 @@ -package io.codeka.gaia.stacks.controller; - -import io.codeka.gaia.modules.bo.TerraformImage; -import io.codeka.gaia.modules.bo.TerraformModule; -import io.codeka.gaia.modules.repository.TerraformModuleRepository; -import io.codeka.gaia.runner.StackRunner; -import io.codeka.gaia.stacks.bo.Job; -import io.codeka.gaia.stacks.bo.JobType; -import io.codeka.gaia.stacks.bo.Stack; -import io.codeka.gaia.stacks.repository.JobRepository; -import io.codeka.gaia.stacks.repository.StackRepository; -import io.codeka.gaia.teams.User; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.ui.Model; - -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class StackControllerTest { - - private StackController controller; - - @Mock - private StackRepository stackRepository; - - @Mock - private StackRunner stackRunner; - - @Mock - private TerraformModuleRepository terraformModuleRepository; - - @Mock - private JobRepository jobRepository; - - @Mock - private Model model; - - private User user = new User("test_user", null); - - @BeforeEach - void setup() { - controller = new StackController(stackRepository, stackRunner, terraformModuleRepository, jobRepository); - } - - @Test - void listStack_shouldReturnToTheView() { - var result = controller.listStacks(); - - assertEquals("stacks", result); - } - - @Test - void launchJob_shouldReturnTheView() { - when(stackRepository.findById(anyString())).thenReturn(Optional.of(new Stack())); - when(terraformModuleRepository.findById(any())).thenReturn(Optional.of(new TerraformModule())); - var result = controller.launchJob("test_stack", JobType.RUN, model, user); - - assertEquals("job", result); - } - - @Test - void launchJob_shouldSetModelIfStackExists() { - when(stackRepository.findById(anyString())).thenReturn(Optional.of(new Stack())); - when(terraformModuleRepository.findById(any())).thenReturn(Optional.of(new TerraformModule())); - var result = controller.launchJob("test_stack", JobType.RUN, model, user); - - assertEquals("job", result); - verify(model).addAttribute("stackId", "test_stack"); - } - - @Test - void launchJob_shouldConfigureAndSaveTheJob() { - var stack = new Stack(); - var module = new TerraformModule(); - module.setTerraformImage(TerraformImage.Companion.defaultInstance()); - - when(stackRepository.findById(anyString())).thenReturn(Optional.of(stack)); - when(terraformModuleRepository.findById(any())).thenReturn(Optional.of(module)); - var result = controller.launchJob("test_stack", JobType.RUN, model, user); - - assertEquals("job", result); - - var captor = ArgumentCaptor.forClass(Job.class); - verify(jobRepository).save(captor.capture()); - var job = captor.getValue(); - assertNotNull(job); - assertEquals(JobType.RUN, job.getType()); - assertEquals("test_stack", job.getStackId()); - assertEquals(user, job.getUser()); - assertEquals(module.getTerraformImage(), job.getTerraformImage()); - } - - @Test - void viewJob_shouldReturnTheView() { - var result = controller.viewJob("test_stack", "test_job", model); - - assertEquals("job", result); - } - - @Test - void viewJob_shouldSetModelIfStackExists() { - when(stackRepository.existsById(anyString())).thenReturn(true); - var result = controller.viewJob("test_stack", "test_job", model); - - assertEquals("job", result); - verify(model).addAttribute("stackId", "test_stack"); - } - - @Test - void viewJob_shouldSetModelIfJobExists() { - when(jobRepository.existsById(anyString())).thenReturn(true); - var result = controller.viewJob("test_stack", "test_job", model); - - assertEquals("job", result); - verify(model).addAttribute("jobId", "test_job"); - } - - @Test - void viewJob_shouldSetEditionMode() { - var result = controller.viewJob("test_stack", "test_job", model); - - assertEquals("job", result); - verify(model).addAttribute("edition", true); - } - - @Test - void getJob_shouldReturnTheJob() { - var job = new Job(); - - when(stackRunner.getJob(anyString())).thenReturn(job); - var result = controller.getJob("test_stack", "test_job"); - - assertEquals(job, result); - } - -} diff --git a/src/test/java/io/codeka/gaia/stacks/controller/StackRestControllerTest.java b/src/test/java/io/codeka/gaia/stacks/controller/StackRestControllerTest.java index fad2813d4..d60534e9b 100644 --- a/src/test/java/io/codeka/gaia/stacks/controller/StackRestControllerTest.java +++ b/src/test/java/io/codeka/gaia/stacks/controller/StackRestControllerTest.java @@ -1,12 +1,19 @@ package io.codeka.gaia.stacks.controller; +import io.codeka.gaia.modules.bo.TerraformImage; +import io.codeka.gaia.modules.bo.TerraformModule; +import io.codeka.gaia.modules.repository.TerraformModuleRepository; +import io.codeka.gaia.stacks.bo.Job; +import io.codeka.gaia.stacks.bo.JobType; import io.codeka.gaia.stacks.bo.Stack; +import io.codeka.gaia.stacks.repository.JobRepository; import io.codeka.gaia.stacks.repository.StackRepository; import io.codeka.gaia.stacks.service.StackCostCalculator; import io.codeka.gaia.teams.Team; import io.codeka.gaia.teams.User; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -15,7 +22,11 @@ import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.assertj.core.api.Assertions.entry; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentCaptor.forClass; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -41,8 +52,14 @@ class StackRestControllerTest { @Mock private StackCostCalculator stackCostCalculator; + @Mock + private TerraformModuleRepository terraformModuleRepository; + + @Mock + private JobRepository jobRepository; + @Test - void listStack_shouldFindAllStacks_forAdminUser(){ + void listStack_shouldFindAllStacks_forAdminUser() { // when stackRestController.listStacks(adminUser); @@ -51,7 +68,7 @@ void listStack_shouldFindAllStacks_forAdminUser(){ } @Test - void listStack_shouldFindTeamStacks_forStandardUser(){ + void listStack_shouldFindTeamStacks_forStandardUser() { // when stackRestController.listStacks(standardUser); @@ -60,7 +77,7 @@ void listStack_shouldFindTeamStacks_forStandardUser(){ } @Test - void listStack_shouldFindOwnedStacks_forUserWithNoTeam(){ + void listStack_shouldFindOwnedStacks_forUserWithNoTeam() { // when stackRestController.listStacks(userWithNoTeam); @@ -69,7 +86,7 @@ void listStack_shouldFindOwnedStacks_forUserWithNoTeam(){ } @Test - void getStack_shouldFindStack_forAdminUser(){ + void getStack_shouldFindStack_forAdminUser() { // given when(stackRepository.findById("42")).thenReturn(Optional.of(stack)); @@ -81,7 +98,7 @@ void getStack_shouldFindStack_forAdminUser(){ } @Test - void getStack_shouldFindStack_forStandardUser(){ + void getStack_shouldFindStack_forStandardUser() { // given when(stackRepository.findByIdAndOwnerTeam("42", userTeam)).thenReturn(Optional.of(stack)); @@ -93,7 +110,7 @@ void getStack_shouldFindStack_forStandardUser(){ } @Test - void getStack_shouldFindStack_forUserWithNoTeam(){ + void getStack_shouldFindStack_forUserWithNoTeam() { // given when(stackRepository.findById("42")).thenReturn(Optional.of(stack)); @@ -105,7 +122,7 @@ void getStack_shouldFindStack_forUserWithNoTeam(){ } @Test - void getStack_shouldCalculateRunningCost_forStandardUser(){ + void getStack_shouldCalculateRunningCost_forStandardUser() { // given when(stackRepository.findByIdAndOwnerTeam("42", userTeam)).thenReturn(Optional.of(stack)); @@ -118,7 +135,7 @@ void getStack_shouldCalculateRunningCost_forStandardUser(){ } @Test - void getStack_shouldThrowStackNotFoundException(){ + void getStack_shouldThrowStackNotFoundException() { // given when(stackRepository.findById("12")).thenReturn(Optional.empty()); when(stackRepository.findByIdAndOwnerTeam("42", userTeam)).thenReturn(Optional.empty()); @@ -129,7 +146,7 @@ void getStack_shouldThrowStackNotFoundException(){ } @Test - void save_shouldSaveStack(){ + void save_shouldSaveStack() { // when stackRestController.save(stack, userTeam, standardUser); @@ -142,7 +159,7 @@ void save_shouldSaveStack(){ } @Test - void update_shouldSaveStack(){ + void update_shouldSaveStack() { // when stackRestController.update("12", stack, standardUser); @@ -151,4 +168,33 @@ void update_shouldSaveStack(){ assertThat(stack.getUpdatedAt()).isEqualToIgnoringSeconds(LocalDateTime.now()); verify(stackRepository).save(stack); } + + @Test + void launchJob_shouldConfigureAndSaveTheJob() { + // given + var stack = new Stack(); + var module = new TerraformModule(); + module.setTerraformImage(TerraformImage.Companion.defaultInstance()); + var user = new User("test_user", null); + + // when + when(stackRepository.findById(anyString())).thenReturn(Optional.of(stack)); + when(terraformModuleRepository.findById(any())).thenReturn(Optional.of(module)); + var result = stackRestController.launchJob("test_stack", JobType.RUN, user); + + // then + assertThat(result) + .isNotNull() + .containsKeys("jobId"); + + var captor = forClass(Job.class); + verify(jobRepository).save(captor.capture()); + var job = captor.getValue(); + assertNotNull(job); + assertEquals(JobType.RUN, job.getType()); + assertEquals("test_stack", job.getStackId()); + assertEquals(user, job.getUser()); + assertEquals(module.getTerraformImage(), job.getTerraformImage()); + } + }