From 2e3d2192943436764c54d455be71d7a9f858d4c9 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Sun, 28 Jun 2026 23:35:04 +0400 Subject: [PATCH 01/50] feat(security): implement end-to-end security pillar --- .../dev/cleat/api/CleatApiApplication.java | 2 +- .../controller/CodeScanAlertController.java | 28 ++ .../controller/SecretFindingController.java | 28 ++ .../controller/VulnerabilityController.java | 36 +++ .../api/exception/GlobalExceptionHandler.java | 23 ++ .../api/CodeScanAlertControllerTest.java | 45 +++ .../api/SecretFindingControllerTest.java | 45 +++ .../api/VulnerabilityControllerTest.java | 45 +++ backend/apps/worker/build.gradle.kts | 9 +- .../cleat/worker/CleatWorkerApplication.java | 2 +- .../dev/cleat/worker/VulnerabilityWorker.java | 22 ++ .../dto/request/CodeScanAlertRequestDto.java | 133 ++++++++ .../dto/request/VulnerabilityRequestDto.java | 13 + .../response/CodeScanAlertResponseDto.java | 171 ++++++++++ .../cleat/common/dto/response/DatasetDto.java | 12 + .../response/VulnerabilityResponseDto.java | 11 + .../java/dev/cleat/common/enums/Priority.java | 21 ++ .../java/dev/cleat/common/enums/Status.java | 20 ++ backend/libs/domain/build.gradle.kts | 1 + .../dev/cleat/domain/PriorityCalculator.java | 24 ++ .../dev/cleat/domain/model/Vulnerability.java | 5 + .../cleat/persistence/DashboardService.java | 57 +++- .../entity/CodeScanAlertEntity.java | 209 +++++++++++++ .../entity/VulnerabilityEntity.java | 16 + .../persistence/mapper/AccountMapper.java | 38 +++ .../mapper/ActivityEventMapper.java | 38 +++ .../cleat/persistence/mapper/CleatMapper.java | 292 ------------------ .../mapper/CodeScanAlertMapper.java | 50 +++ .../persistence/mapper/MemberMapper.java | 35 +++ .../cleat/persistence/mapper/RepoMapper.java | 62 ++++ .../mapper/ScorecardCheckMapper.java | 20 ++ .../mapper/SecretFindingMapper.java | 44 +++ .../cleat/persistence/mapper/UsageMapper.java | 32 ++ .../persistence/mapper/UsagePointMapper.java | 20 ++ .../mapper/VulnerabilityMapper.java | 53 ++++ .../repository/CodeScanAlertRepository.java | 11 + .../V2__update_repo_and_add_tables.sql | 26 ++ .../V3_alter_vulnerability_table.sql | 1 + .../V4__create_code_scan_alerts_table.sql | 37 +++ .../V5__alter_secret_finding_table.sql | 3 + backend/libs/scanning/build.gradle.kts | 5 + .../cleat/scanning/CodeScanAlertScanner.java | 31 ++ .../cleat/scanning/SecretFindingScanner.java | 32 ++ .../cleat/scanning/VulnerabilityScanner.java | 40 +++ .../service/CodeScanAlertService.java | 28 ++ .../service/SecretFindingService.java | 29 ++ .../service/VulnerabilityService.java | 48 +++ .../scanning/CodeScanAlertScannerTest.java | 39 +++ .../scanning/SecretFindingScannerTest.java | 59 ++++ .../scanning/VulnerabilityScannerTest.java | 69 +++++ 50 files changed, 1811 insertions(+), 309 deletions(-) create mode 100644 backend/apps/api/src/main/java/dev/cleat/api/controller/CodeScanAlertController.java create mode 100644 backend/apps/api/src/main/java/dev/cleat/api/controller/SecretFindingController.java create mode 100644 backend/apps/api/src/main/java/dev/cleat/api/controller/VulnerabilityController.java create mode 100644 backend/apps/api/src/main/java/dev/cleat/api/exception/GlobalExceptionHandler.java create mode 100644 backend/apps/api/src/test/java/dev/cleat/api/CodeScanAlertControllerTest.java create mode 100644 backend/apps/api/src/test/java/dev/cleat/api/SecretFindingControllerTest.java create mode 100644 backend/apps/api/src/test/java/dev/cleat/api/VulnerabilityControllerTest.java create mode 100644 backend/apps/worker/src/main/java/dev/cleat/worker/VulnerabilityWorker.java create mode 100644 backend/libs/common/src/main/java/dev/cleat/common/dto/request/CodeScanAlertRequestDto.java create mode 100644 backend/libs/common/src/main/java/dev/cleat/common/dto/response/CodeScanAlertResponseDto.java create mode 100644 backend/libs/common/src/main/java/dev/cleat/common/enums/Priority.java create mode 100644 backend/libs/common/src/main/java/dev/cleat/common/enums/Status.java create mode 100644 backend/libs/domain/src/main/java/dev/cleat/domain/PriorityCalculator.java create mode 100644 backend/libs/domain/src/main/java/dev/cleat/domain/model/Vulnerability.java create mode 100644 backend/libs/persistence/src/main/java/dev/cleat/persistence/entity/CodeScanAlertEntity.java create mode 100644 backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/AccountMapper.java create mode 100644 backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/ActivityEventMapper.java delete mode 100644 backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/CleatMapper.java create mode 100644 backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/CodeScanAlertMapper.java create mode 100644 backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/MemberMapper.java create mode 100644 backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/RepoMapper.java create mode 100644 backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/ScorecardCheckMapper.java create mode 100644 backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/SecretFindingMapper.java create mode 100644 backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/UsageMapper.java create mode 100644 backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/UsagePointMapper.java create mode 100644 backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/VulnerabilityMapper.java create mode 100644 backend/libs/persistence/src/main/java/dev/cleat/persistence/repository/CodeScanAlertRepository.java create mode 100644 backend/libs/persistence/src/main/resources/db/migration/V3_alter_vulnerability_table.sql create mode 100644 backend/libs/persistence/src/main/resources/db/migration/V4__create_code_scan_alerts_table.sql create mode 100644 backend/libs/persistence/src/main/resources/db/migration/V5__alter_secret_finding_table.sql create mode 100644 backend/libs/scanning/src/main/java/dev/cleat/scanning/CodeScanAlertScanner.java create mode 100644 backend/libs/scanning/src/main/java/dev/cleat/scanning/SecretFindingScanner.java create mode 100644 backend/libs/scanning/src/main/java/dev/cleat/scanning/VulnerabilityScanner.java create mode 100644 backend/libs/scanning/src/main/java/dev/cleat/scanning/service/CodeScanAlertService.java create mode 100644 backend/libs/scanning/src/main/java/dev/cleat/scanning/service/SecretFindingService.java create mode 100644 backend/libs/scanning/src/main/java/dev/cleat/scanning/service/VulnerabilityService.java create mode 100644 backend/libs/scanning/src/test/java/dev/cleat/scanning/CodeScanAlertScannerTest.java create mode 100644 backend/libs/scanning/src/test/java/dev/cleat/scanning/SecretFindingScannerTest.java create mode 100644 backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java diff --git a/backend/apps/api/src/main/java/dev/cleat/api/CleatApiApplication.java b/backend/apps/api/src/main/java/dev/cleat/api/CleatApiApplication.java index 78331baf..8fdd8836 100644 --- a/backend/apps/api/src/main/java/dev/cleat/api/CleatApiApplication.java +++ b/backend/apps/api/src/main/java/dev/cleat/api/CleatApiApplication.java @@ -5,7 +5,7 @@ import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -@SpringBootApplication(scanBasePackages = {"dev.cleat.api", "dev.cleat.persistence", "dev.cleat.common"}) +@SpringBootApplication(scanBasePackages = {"dev.cleat"}) @EnableJpaRepositories(basePackages = "dev.cleat.persistence.repository") @EntityScan(basePackages = "dev.cleat.persistence.entity") public class CleatApiApplication { diff --git a/backend/apps/api/src/main/java/dev/cleat/api/controller/CodeScanAlertController.java b/backend/apps/api/src/main/java/dev/cleat/api/controller/CodeScanAlertController.java new file mode 100644 index 00000000..2cc7198a --- /dev/null +++ b/backend/apps/api/src/main/java/dev/cleat/api/controller/CodeScanAlertController.java @@ -0,0 +1,28 @@ +package dev.cleat.api.controller; + +import dev.cleat.common.dto.request.CodeScanAlertRequestDto; +import dev.cleat.common.dto.response.CodeScanAlertResponseDto; +import dev.cleat.scanning.service.CodeScanAlertService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +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.RestController; + +@RestController +@RequestMapping("/api/v1/alerts") +public class CodeScanAlertController { + + private final CodeScanAlertService codeScanAlertService; + + public CodeScanAlertController(CodeScanAlertService codeScanAlertService) { + this.codeScanAlertService = codeScanAlertService; + } + + @PostMapping + public ResponseEntity create( + @RequestBody CodeScanAlertRequestDto codeScanAlertRequestDto) { + return ResponseEntity.status(HttpStatus.CREATED).body(codeScanAlertService.create(codeScanAlertRequestDto)); + } +} diff --git a/backend/apps/api/src/main/java/dev/cleat/api/controller/SecretFindingController.java b/backend/apps/api/src/main/java/dev/cleat/api/controller/SecretFindingController.java new file mode 100644 index 00000000..f3c6e0f4 --- /dev/null +++ b/backend/apps/api/src/main/java/dev/cleat/api/controller/SecretFindingController.java @@ -0,0 +1,28 @@ +package dev.cleat.api.controller; + +import dev.cleat.common.dto.request.SecretFindingRequestDto; +import dev.cleat.common.dto.response.SecretFindingResponseDto; +import dev.cleat.scanning.service.SecretFindingService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +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.RestController; + +@RestController +@RequestMapping("/api/v1/secrets") +public class SecretFindingController { + + private final SecretFindingService secretFindingService; + + public SecretFindingController(SecretFindingService secretFindingService) { + this.secretFindingService = secretFindingService; + } + + @PostMapping + public ResponseEntity createSecret( + @RequestBody SecretFindingRequestDto secretFindingRequestDto) { + return ResponseEntity.status(HttpStatus.CREATED).body(secretFindingService.create(secretFindingRequestDto)); + } +} diff --git a/backend/apps/api/src/main/java/dev/cleat/api/controller/VulnerabilityController.java b/backend/apps/api/src/main/java/dev/cleat/api/controller/VulnerabilityController.java new file mode 100644 index 00000000..46c03177 --- /dev/null +++ b/backend/apps/api/src/main/java/dev/cleat/api/controller/VulnerabilityController.java @@ -0,0 +1,36 @@ +package dev.cleat.api.controller; + +import dev.cleat.common.dto.request.VulnerabilityRequestDto; +import dev.cleat.common.dto.response.VulnerabilityResponseDto; +import dev.cleat.scanning.service.VulnerabilityService; +import java.util.UUID; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +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.RestController; + +@RestController +@RequestMapping("/api/v1/vulnerabilities") +public class VulnerabilityController { + + private final VulnerabilityService vulnerabilityService; + + public VulnerabilityController(VulnerabilityService vulnerabilityService) { + this.vulnerabilityService = vulnerabilityService; + } + + @PostMapping + public ResponseEntity createVulnerability( + @RequestBody VulnerabilityRequestDto vulnerabilityRequestDto) { + return ResponseEntity.status(HttpStatus.CREATED).body(vulnerabilityService.create(vulnerabilityRequestDto)); + } + + @GetMapping("/{id}") + public ResponseEntity getVulnerability(@PathVariable UUID id) { + return ResponseEntity.ok(vulnerabilityService.findById(id)); + } +} diff --git a/backend/apps/api/src/main/java/dev/cleat/api/exception/GlobalExceptionHandler.java b/backend/apps/api/src/main/java/dev/cleat/api/exception/GlobalExceptionHandler.java new file mode 100644 index 00000000..71adf3e5 --- /dev/null +++ b/backend/apps/api/src/main/java/dev/cleat/api/exception/GlobalExceptionHandler.java @@ -0,0 +1,23 @@ +package dev.cleat.api.exception; + +import dev.cleat.common.exception.NotFoundException; +import java.util.Map; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(NotFoundException.class) + public ResponseEntity> handleNotFound(NotFoundException ex) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of("error", ex.getMessage())); + } + + @ExceptionHandler(RuntimeException.class) + public ResponseEntity> handleRuntimeException(RuntimeException ex) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(Map.of("error", "An unexpected error occured: " + ex.getMessage())); + } +} diff --git a/backend/apps/api/src/test/java/dev/cleat/api/CodeScanAlertControllerTest.java b/backend/apps/api/src/test/java/dev/cleat/api/CodeScanAlertControllerTest.java new file mode 100644 index 00000000..127d195c --- /dev/null +++ b/backend/apps/api/src/test/java/dev/cleat/api/CodeScanAlertControllerTest.java @@ -0,0 +1,45 @@ +package dev.cleat.api; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.cleat.api.controller.CodeScanAlertController; +import dev.cleat.common.dto.request.CodeScanAlertRequestDto; +import dev.cleat.scanning.service.CodeScanAlertService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(CodeScanAlertController.class) +@AutoConfigureMockMvc(addFilters = false) +@ContextConfiguration(classes = CodeScanAlertController.class) +public class CodeScanAlertControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockitoBean + private CodeScanAlertService codeScanAlertService; + + @Test + void whenCreateCodeScanAlert_thenShouldBeReturn201() throws Exception { + + // given + CodeScanAlertRequestDto codeScanAlertRequestDto = new CodeScanAlertRequestDto(); + + // then + mockMvc.perform(post("/api/v1/alerts") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(codeScanAlertRequestDto))) + .andExpect(status().isCreated()); + } +} diff --git a/backend/apps/api/src/test/java/dev/cleat/api/SecretFindingControllerTest.java b/backend/apps/api/src/test/java/dev/cleat/api/SecretFindingControllerTest.java new file mode 100644 index 00000000..eef29d44 --- /dev/null +++ b/backend/apps/api/src/test/java/dev/cleat/api/SecretFindingControllerTest.java @@ -0,0 +1,45 @@ +package dev.cleat.api; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.cleat.api.controller.SecretFindingController; +import dev.cleat.common.dto.request.SecretFindingRequestDto; +import dev.cleat.scanning.service.SecretFindingService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(SecretFindingController.class) +@AutoConfigureMockMvc(addFilters = false) +@ContextConfiguration(classes = {SecretFindingController.class}) +public class SecretFindingControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockitoBean + private SecretFindingService secretFindingService; + + @Test + void whenCreateSecret_thenShouldReturn201() throws Exception { + + // given + SecretFindingRequestDto secretFindingRequestDto = new SecretFindingRequestDto(); + + // then + mockMvc.perform(post("/api/v1/secrets") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(secretFindingRequestDto))) + .andExpect(status().isCreated()); + } +} diff --git a/backend/apps/api/src/test/java/dev/cleat/api/VulnerabilityControllerTest.java b/backend/apps/api/src/test/java/dev/cleat/api/VulnerabilityControllerTest.java new file mode 100644 index 00000000..9f0555de --- /dev/null +++ b/backend/apps/api/src/test/java/dev/cleat/api/VulnerabilityControllerTest.java @@ -0,0 +1,45 @@ +package dev.cleat.api; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.cleat.api.controller.VulnerabilityController; +import dev.cleat.common.dto.request.VulnerabilityRequestDto; +import dev.cleat.scanning.service.VulnerabilityService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(VulnerabilityController.class) +@AutoConfigureMockMvc(addFilters = false) +@ContextConfiguration(classes = {VulnerabilityController.class}) +public class VulnerabilityControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockitoBean + private VulnerabilityService vulnerabilityService; + + @Test + void whenCreateVulnerability_thenShouldReturn201() throws Exception { + + // given + VulnerabilityRequestDto vulnerabilityRequestDto = new VulnerabilityRequestDto(); + + // then + mockMvc.perform(post("/api/v1/vulnerabilities") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(vulnerabilityRequestDto))) + .andExpect(status().isCreated()); + } +} diff --git a/backend/apps/worker/build.gradle.kts b/backend/apps/worker/build.gradle.kts index 4ae7e159..b190c03b 100644 --- a/backend/apps/worker/build.gradle.kts +++ b/backend/apps/worker/build.gradle.kts @@ -14,8 +14,9 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-actuator") testImplementation("org.springframework.boot:spring-boot-starter-test") - testImplementation("org.testcontainers:junit-jupiter") - testImplementation("org.testcontainers:postgresql") - testImplementation("org.testcontainers:testcontainers") - testImplementation("org.springframework.boot:spring-boot-testcontainers") + testImplementation("org.testcontainers:junit-jupiter") + testImplementation("org.testcontainers:postgresql") + testImplementation("org.testcontainers:testcontainers") + testImplementation("org.springframework.boot:spring-boot-testcontainers") + } diff --git a/backend/apps/worker/src/main/java/dev/cleat/worker/CleatWorkerApplication.java b/backend/apps/worker/src/main/java/dev/cleat/worker/CleatWorkerApplication.java index c28eff76..abb0d983 100644 --- a/backend/apps/worker/src/main/java/dev/cleat/worker/CleatWorkerApplication.java +++ b/backend/apps/worker/src/main/java/dev/cleat/worker/CleatWorkerApplication.java @@ -4,7 +4,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; -@SpringBootApplication +@SpringBootApplication(scanBasePackages = {"dev.cleat.persistence", "dev.cleat.scanning"}) @EnableScheduling public class CleatWorkerApplication { diff --git a/backend/apps/worker/src/main/java/dev/cleat/worker/VulnerabilityWorker.java b/backend/apps/worker/src/main/java/dev/cleat/worker/VulnerabilityWorker.java new file mode 100644 index 00000000..387433e8 --- /dev/null +++ b/backend/apps/worker/src/main/java/dev/cleat/worker/VulnerabilityWorker.java @@ -0,0 +1,22 @@ +package dev.cleat.worker; + +import dev.cleat.persistence.entity.VulnerabilityEntity; +import dev.cleat.scanning.VulnerabilityScanner; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Component +public class VulnerabilityWorker { + private final VulnerabilityScanner vulnerabilityScanner; + private final VulnerabilityEntity vulnerabilityEntity; + + public VulnerabilityWorker(VulnerabilityScanner vulnerabilityScanner, VulnerabilityEntity vulnerabilityEntity) { + this.vulnerabilityScanner = vulnerabilityScanner; + this.vulnerabilityEntity = vulnerabilityEntity; + } + + @Scheduled(fixedDelay = 600000) + public void runSecurityScan() { + vulnerabilityScanner.processAndSave(vulnerabilityEntity); + } +} diff --git a/backend/libs/common/src/main/java/dev/cleat/common/dto/request/CodeScanAlertRequestDto.java b/backend/libs/common/src/main/java/dev/cleat/common/dto/request/CodeScanAlertRequestDto.java new file mode 100644 index 00000000..40e23d51 --- /dev/null +++ b/backend/libs/common/src/main/java/dev/cleat/common/dto/request/CodeScanAlertRequestDto.java @@ -0,0 +1,133 @@ +package dev.cleat.common.dto.request; + +import dev.cleat.common.enums.Severity; +import java.util.UUID; + +public class CodeScanAlertRequestDto { + + private UUID accountId; + private UUID repo; + private String rule; + private UUID ruleId; + private Severity severity; + private String file; + private Integer line; + private String branch; + private String tool; + private String description; + + public CodeScanAlertRequestDto() {} + + public CodeScanAlertRequestDto( + UUID accountId, + UUID repo, + String rule, + UUID ruleId, + Severity severity, + String file, + Integer line, + String branch, + String tool, + String description) { + this.accountId = accountId; + this.repo = repo; + this.rule = rule; + this.ruleId = ruleId; + this.severity = severity; + this.file = file; + this.line = line; + this.branch = branch; + this.tool = tool; + this.description = description; + } + + public UUID getAccountId() { + return accountId; + } + + public CodeScanAlertRequestDto setAccountId(UUID accountId) { + this.accountId = accountId; + return this; + } + + public UUID getRepo() { + return repo; + } + + public CodeScanAlertRequestDto setRepo(UUID repo) { + this.repo = repo; + return this; + } + + public String getRule() { + return rule; + } + + public CodeScanAlertRequestDto setRule(String rule) { + this.rule = rule; + return this; + } + + public UUID getRuleId() { + return ruleId; + } + + public CodeScanAlertRequestDto setRuleId(UUID ruleId) { + this.ruleId = ruleId; + return this; + } + + public Severity getSeverity() { + return severity; + } + + public CodeScanAlertRequestDto setSeverity(Severity severity) { + this.severity = severity; + return this; + } + + public String getFile() { + return file; + } + + public CodeScanAlertRequestDto setFile(String file) { + this.file = file; + return this; + } + + public Integer getLine() { + return line; + } + + public CodeScanAlertRequestDto setLine(Integer line) { + this.line = line; + return this; + } + + public String getBranch() { + return branch; + } + + public CodeScanAlertRequestDto setBranch(String branch) { + this.branch = branch; + return this; + } + + public String getTool() { + return tool; + } + + public CodeScanAlertRequestDto setTool(String tool) { + this.tool = tool; + return this; + } + + public String getDescription() { + return description; + } + + public CodeScanAlertRequestDto setDescription(String description) { + this.description = description; + return this; + } +} diff --git a/backend/libs/common/src/main/java/dev/cleat/common/dto/request/VulnerabilityRequestDto.java b/backend/libs/common/src/main/java/dev/cleat/common/dto/request/VulnerabilityRequestDto.java index ebb420fe..1ca50ebb 100644 --- a/backend/libs/common/src/main/java/dev/cleat/common/dto/request/VulnerabilityRequestDto.java +++ b/backend/libs/common/src/main/java/dev/cleat/common/dto/request/VulnerabilityRequestDto.java @@ -1,5 +1,6 @@ package dev.cleat.common.dto.request; +import dev.cleat.common.enums.Priority; import dev.cleat.common.enums.Reachable; import dev.cleat.common.enums.Severity; import java.util.List; @@ -13,6 +14,7 @@ public class VulnerabilityRequestDto { private String fixedVersion; private Double cvss; private Severity severity; + private Priority priority; private Double epss; private Boolean kev; private Reachable reachable; @@ -31,6 +33,7 @@ public VulnerabilityRequestDto( String fixedVersion, Double cvss, Severity severity, + Priority priority, Double epss, Boolean kev, Reachable reachable, @@ -46,6 +49,7 @@ public VulnerabilityRequestDto( this.fixedVersion = fixedVersion; this.cvss = cvss; this.severity = severity; + this.priority = priority; this.epss = epss; this.kev = kev; this.reachable = reachable; @@ -110,6 +114,15 @@ public VulnerabilityRequestDto setSeverity(Severity severity) { return this; } + public Priority getPriority() { + return priority; + } + + public VulnerabilityRequestDto setPriority(Priority priority) { + this.priority = priority; + return this; + } + public Double getEpss() { return epss; } diff --git a/backend/libs/common/src/main/java/dev/cleat/common/dto/response/CodeScanAlertResponseDto.java b/backend/libs/common/src/main/java/dev/cleat/common/dto/response/CodeScanAlertResponseDto.java new file mode 100644 index 00000000..ba919bdb --- /dev/null +++ b/backend/libs/common/src/main/java/dev/cleat/common/dto/response/CodeScanAlertResponseDto.java @@ -0,0 +1,171 @@ +package dev.cleat.common.dto.response; + +import dev.cleat.common.enums.Severity; +import dev.cleat.common.enums.Status; +import java.time.OffsetDateTime; +import java.util.UUID; + +public class CodeScanAlertResponseDto { + + private UUID id; + private UUID accountId; + private UUID repo; + private String rule; + private UUID ruleId; + private Severity severity; + private String file; + private Integer line; + private String branch; + private Status status; + private String tool; + private OffsetDateTime detectedAt; + private String description; + + public CodeScanAlertResponseDto() {} + + public CodeScanAlertResponseDto( + UUID id, + UUID accountId, + UUID repo, + String rule, + UUID ruleId, + Severity severity, + String file, + Integer line, + String branch, + Status status, + String tool, + OffsetDateTime detectedAt, + String description) { + this.id = id; + this.accountId = accountId; + this.repo = repo; + this.rule = rule; + this.ruleId = ruleId; + this.severity = severity; + this.file = file; + this.line = line; + this.branch = branch; + this.status = status; + this.tool = tool; + this.detectedAt = detectedAt; + this.description = description; + } + + public UUID getId() { + return id; + } + + public CodeScanAlertResponseDto setId(UUID id) { + this.id = id; + return this; + } + + public UUID getAccountId() { + return accountId; + } + + public CodeScanAlertResponseDto setAccountId(UUID accountId) { + this.accountId = accountId; + return this; + } + + public UUID getRepo() { + return repo; + } + + public CodeScanAlertResponseDto setRepo(UUID repo) { + this.repo = repo; + return this; + } + + public String getRule() { + return rule; + } + + public CodeScanAlertResponseDto setRule(String rule) { + this.rule = rule; + return this; + } + + public UUID getRuleId() { + return ruleId; + } + + public CodeScanAlertResponseDto setRuleId(UUID ruleId) { + this.ruleId = ruleId; + return this; + } + + public Severity getSeverity() { + return severity; + } + + public CodeScanAlertResponseDto setSeverity(Severity severity) { + this.severity = severity; + return this; + } + + public String getFile() { + return file; + } + + public CodeScanAlertResponseDto setFile(String file) { + this.file = file; + return this; + } + + public Integer getLine() { + return line; + } + + public CodeScanAlertResponseDto setLine(Integer line) { + this.line = line; + return this; + } + + public String getBranch() { + return branch; + } + + public CodeScanAlertResponseDto setBranch(String branch) { + this.branch = branch; + return this; + } + + public Status getStatus() { + return status; + } + + public CodeScanAlertResponseDto setStatus(Status status) { + this.status = status; + return this; + } + + public String getTool() { + return tool; + } + + public CodeScanAlertResponseDto setTool(String tool) { + this.tool = tool; + return this; + } + + public OffsetDateTime getDetectedAt() { + return detectedAt; + } + + public CodeScanAlertResponseDto setDetectedAt(OffsetDateTime detectedAt) { + this.detectedAt = detectedAt; + return this; + } + + public String getDescription() { + return description; + } + + public CodeScanAlertResponseDto setDescription(String description) { + this.description = description; + return this; + } +} diff --git a/backend/libs/common/src/main/java/dev/cleat/common/dto/response/DatasetDto.java b/backend/libs/common/src/main/java/dev/cleat/common/dto/response/DatasetDto.java index 0e380a7f..ba7eb54c 100644 --- a/backend/libs/common/src/main/java/dev/cleat/common/dto/response/DatasetDto.java +++ b/backend/libs/common/src/main/java/dev/cleat/common/dto/response/DatasetDto.java @@ -8,6 +8,7 @@ public class DatasetDto { private List repos; private List secrets; private List vulnerabilities; + private List codeAlerts; private UsageResponseDto usage; private List members; private List events; @@ -19,6 +20,7 @@ public DatasetDto( List repos, List secrets, List vulnerabilities, + List codeAlerts, UsageResponseDto usage, List members, List events) { @@ -26,6 +28,7 @@ public DatasetDto( this.repos = repos; this.secrets = secrets; this.vulnerabilities = vulnerabilities; + this.codeAlerts = codeAlerts; this.usage = usage; this.members = members; this.events = events; @@ -67,6 +70,15 @@ public DatasetDto setVulnerabilities(List vulnerabilit return this; } + public List getCodeAlerts() { + return codeAlerts; + } + + public DatasetDto setCodeAlerts(List codeAlerts) { + this.codeAlerts = codeAlerts; + return this; + } + public UsageResponseDto getUsage() { return usage; } diff --git a/backend/libs/common/src/main/java/dev/cleat/common/dto/response/VulnerabilityResponseDto.java b/backend/libs/common/src/main/java/dev/cleat/common/dto/response/VulnerabilityResponseDto.java index 12f2280b..ed3f8b0b 100644 --- a/backend/libs/common/src/main/java/dev/cleat/common/dto/response/VulnerabilityResponseDto.java +++ b/backend/libs/common/src/main/java/dev/cleat/common/dto/response/VulnerabilityResponseDto.java @@ -1,5 +1,6 @@ package dev.cleat.common.dto.response; +import dev.cleat.common.enums.Priority; import dev.cleat.common.enums.Reachable; import dev.cleat.common.enums.Severity; import java.time.OffsetDateTime; @@ -15,6 +16,7 @@ public class VulnerabilityResponseDto { private String fixedVersion; private Double cvss; private Severity severity; + private Priority priority; private Double epss; private Boolean kev; private Reachable reachable; @@ -97,6 +99,15 @@ public VulnerabilityResponseDto setSeverity(Severity severity) { return this; } + public Priority getPriority() { + return priority; + } + + public VulnerabilityResponseDto setPriority(Priority priority) { + this.priority = priority; + return this; + } + public Double getEpss() { return epss; } diff --git a/backend/libs/common/src/main/java/dev/cleat/common/enums/Priority.java b/backend/libs/common/src/main/java/dev/cleat/common/enums/Priority.java new file mode 100644 index 00000000..e209a863 --- /dev/null +++ b/backend/libs/common/src/main/java/dev/cleat/common/enums/Priority.java @@ -0,0 +1,21 @@ +package dev.cleat.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum Priority { + URGENT("urgent"), + HIGH("high"), + MEDIUM("medium"), + LOW("low"); + + private final String value; + + Priority(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } +} diff --git a/backend/libs/common/src/main/java/dev/cleat/common/enums/Status.java b/backend/libs/common/src/main/java/dev/cleat/common/enums/Status.java new file mode 100644 index 00000000..8b89b81a --- /dev/null +++ b/backend/libs/common/src/main/java/dev/cleat/common/enums/Status.java @@ -0,0 +1,20 @@ +package dev.cleat.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum Status { + OPEN("open"), + FIXED("fixed"), + DISMISSED("dismissed"); + + private final String value; + + Status(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } +} diff --git a/backend/libs/domain/build.gradle.kts b/backend/libs/domain/build.gradle.kts index 7f5e077b..25a798f4 100644 --- a/backend/libs/domain/build.gradle.kts +++ b/backend/libs/domain/build.gradle.kts @@ -5,4 +5,5 @@ plugins { dependencies { implementation("org.springframework.boot:spring-boot-starter-web") testImplementation("org.springframework.boot:spring-boot-starter-test") + implementation(project(":libs:common")) } diff --git a/backend/libs/domain/src/main/java/dev/cleat/domain/PriorityCalculator.java b/backend/libs/domain/src/main/java/dev/cleat/domain/PriorityCalculator.java new file mode 100644 index 00000000..5111772a --- /dev/null +++ b/backend/libs/domain/src/main/java/dev/cleat/domain/PriorityCalculator.java @@ -0,0 +1,24 @@ +package dev.cleat.domain; + +import dev.cleat.common.enums.Priority; +import dev.cleat.domain.model.Vulnerability; +import org.springframework.stereotype.Component; + +@Component +public class PriorityCalculator { + public Priority calculate(Vulnerability vulnerability) { + + if (vulnerability.cvss() < 0 || vulnerability.cvss() > 10) { + throw new IllegalArgumentException("CVSS must be between 0 and 10"); + } + if (Boolean.TRUE.equals(vulnerability.kev()) || vulnerability.cvss() != null && vulnerability.cvss() >= 9.0) { + return Priority.URGENT; + } + + return switch (vulnerability.severity()) { + case CRITICAL -> Priority.HIGH; + case HIGH -> Priority.MEDIUM; + default -> Priority.LOW; + }; + } +} diff --git a/backend/libs/domain/src/main/java/dev/cleat/domain/model/Vulnerability.java b/backend/libs/domain/src/main/java/dev/cleat/domain/model/Vulnerability.java new file mode 100644 index 00000000..36c1e1fe --- /dev/null +++ b/backend/libs/domain/src/main/java/dev/cleat/domain/model/Vulnerability.java @@ -0,0 +1,5 @@ +package dev.cleat.domain.model; + +import dev.cleat.common.enums.Severity; + +public record Vulnerability(Boolean kev, Double cvss, Severity severity) {} diff --git a/backend/libs/persistence/src/main/java/dev/cleat/persistence/DashboardService.java b/backend/libs/persistence/src/main/java/dev/cleat/persistence/DashboardService.java index 01f85ce0..3763f717 100644 --- a/backend/libs/persistence/src/main/java/dev/cleat/persistence/DashboardService.java +++ b/backend/libs/persistence/src/main/java/dev/cleat/persistence/DashboardService.java @@ -4,9 +4,17 @@ import dev.cleat.common.exception.NotFoundException; import dev.cleat.persistence.entity.AccountEntity; import dev.cleat.persistence.entity.UsageEntity; -import dev.cleat.persistence.mapper.CleatMapper; +import dev.cleat.persistence.mapper.AccountMapper; +import dev.cleat.persistence.mapper.ActivityEventMapper; +import dev.cleat.persistence.mapper.CodeScanAlertMapper; +import dev.cleat.persistence.mapper.MemberMapper; +import dev.cleat.persistence.mapper.RepoMapper; +import dev.cleat.persistence.mapper.SecretFindingMapper; +import dev.cleat.persistence.mapper.UsageMapper; +import dev.cleat.persistence.mapper.VulnerabilityMapper; import dev.cleat.persistence.repository.AccountRepository; import dev.cleat.persistence.repository.ActivityEventRepository; +import dev.cleat.persistence.repository.CodeScanAlertRepository; import dev.cleat.persistence.repository.MemberRepository; import dev.cleat.persistence.repository.RepoRepository; import dev.cleat.persistence.repository.SecretFindingRepository; @@ -25,7 +33,15 @@ public class DashboardService { private final UsageRepository usageRepository; private final MemberRepository memberRepository; private final ActivityEventRepository activityEventRepository; - private final CleatMapper cleatMapper; + private final AccountMapper accountMapper; + private final ActivityEventMapper activityEventMapper; + private final MemberMapper memberMapper; + private final RepoMapper repoMapper; + private final UsageMapper usageMapper; + private final SecretFindingMapper secretFindingMapper; + private final VulnerabilityMapper vulnerabilityMapper; + private final CodeScanAlertMapper codeScanAlertMapper; + private final CodeScanAlertRepository codeScanAlertRepository; public DashboardService( AccountRepository accountRepository, @@ -35,7 +51,15 @@ public DashboardService( UsageRepository usageRepository, MemberRepository memberRepository, ActivityEventRepository activityEventRepository, - CleatMapper cleatMapper) { + AccountMapper accountMapper, + ActivityEventMapper activityEventMapper, + MemberMapper memberMapper, + RepoMapper repoMapper, + UsageMapper usageMapper, + SecretFindingMapper secretFindingMapper, + VulnerabilityMapper vulnerabilityMapper, + CodeScanAlertMapper codeScanAlertMapper, + CodeScanAlertRepository codeScanAlertRepository) { this.accountRepository = accountRepository; this.repoRepository = repoRepository; this.secretFindingRepository = secretFindingRepository; @@ -43,7 +67,15 @@ public DashboardService( this.usageRepository = usageRepository; this.memberRepository = memberRepository; this.activityEventRepository = activityEventRepository; - this.cleatMapper = cleatMapper; + this.accountMapper = accountMapper; + this.activityEventMapper = activityEventMapper; + this.memberMapper = memberMapper; + this.repoMapper = repoMapper; + this.usageMapper = usageMapper; + this.secretFindingMapper = secretFindingMapper; + this.vulnerabilityMapper = vulnerabilityMapper; + this.codeScanAlertMapper = codeScanAlertMapper; + this.codeScanAlertRepository = codeScanAlertRepository; } @Transactional(readOnly = true) @@ -52,23 +84,26 @@ public DatasetDto getDataset(UUID accountId) { AccountEntity account = accountRepository.findById(accountId).orElseThrow(() -> new NotFoundException("Account not found")); return new DatasetDto( - cleatMapper.toAccountDto(account), + accountMapper.toAccountDto(account), repoRepository.findAllByAccountId(accountId).stream() - .map(cleatMapper::toRepoDto) + .map(repoMapper::toRepoDto) .toList(), secretFindingRepository.findAllByAccountId(accountId).stream() - .map(cleatMapper::toSecretFindingDto) + .map(secretFindingMapper::toSecretFindingDto) .toList(), vulnerabilityRepository.findAllByAccountId(accountId).stream() - .map(cleatMapper::toVulnerabilityDto) + .map(vulnerabilityMapper::toVulnerabilityDto) .toList(), - cleatMapper.toUsageDto( + codeScanAlertRepository.findAllByAccountId(accountId).stream() + .map(codeScanAlertMapper::toCodeScanAlertDto) + .toList(), + usageMapper.toUsageDto( usageRepository.findByAccountId(accountId).orElse(new UsageEntity())), memberRepository.findAllByAccountId(accountId).stream() - .map(cleatMapper::toMemberDto) + .map(memberMapper::toMemberDto) .toList(), activityEventRepository.findAllByAccountId(accountId).stream() - .map(cleatMapper::toActivityEventDto) + .map(activityEventMapper::toActivityEventDto) .toList()); } } diff --git a/backend/libs/persistence/src/main/java/dev/cleat/persistence/entity/CodeScanAlertEntity.java b/backend/libs/persistence/src/main/java/dev/cleat/persistence/entity/CodeScanAlertEntity.java new file mode 100644 index 00000000..fe633e22 --- /dev/null +++ b/backend/libs/persistence/src/main/java/dev/cleat/persistence/entity/CodeScanAlertEntity.java @@ -0,0 +1,209 @@ +package dev.cleat.persistence.entity; + +import dev.cleat.common.enums.Severity; +import dev.cleat.common.enums.Status; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.time.OffsetDateTime; +import java.util.UUID; + +@Entity +@Table(name = "code_scan_alerts") +public class CodeScanAlertEntity { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + @Column(name = "account_id") + private UUID accountId; + + @Column(name = "repo") + private UUID repo; + + @Column(name = "rule") + private String rule; + + @Column(name = "rule_id") + private UUID ruleId; + + @Enumerated(EnumType.STRING) + @Column(name = "severity") + private Severity severity; + + @Column(name = "file") + private String file; + + @Column(name = "line") + private Integer line; + + @Column(name = "branch") + private String branch; + + @Column(name = "status") + @Enumerated(EnumType.STRING) + private Status status; + + @Column(name = "tool") + private String tool; + + @Column(name = "detected_at") + private OffsetDateTime detectedAt; + + @Column(name = "description") + private String description; + + public CodeScanAlertEntity() {} + + public CodeScanAlertEntity( + UUID id, + UUID accountId, + UUID repo, + String rule, + UUID ruleId, + Severity severity, + String file, + Integer line, + String branch, + Status status, + String tool, + OffsetDateTime detectedAt, + String description) { + this.id = id; + this.accountId = accountId; + this.repo = repo; + this.rule = rule; + this.ruleId = ruleId; + this.severity = severity; + this.file = file; + this.line = line; + this.branch = branch; + this.status = status; + this.tool = tool; + this.detectedAt = detectedAt; + this.description = description; + } + + public UUID getId() { + return id; + } + + public CodeScanAlertEntity setId(UUID id) { + this.id = id; + return this; + } + + public UUID getAccountId() { + return accountId; + } + + public CodeScanAlertEntity setAccountId(UUID accountId) { + this.accountId = accountId; + return this; + } + + public UUID getRepo() { + return repo; + } + + public CodeScanAlertEntity setRepo(UUID repo) { + this.repo = repo; + return this; + } + + public String getRule() { + return rule; + } + + public CodeScanAlertEntity setRule(String rule) { + this.rule = rule; + return this; + } + + public UUID getRuleId() { + return ruleId; + } + + public CodeScanAlertEntity setRuleId(UUID ruleId) { + this.ruleId = ruleId; + return this; + } + + public Severity getSeverity() { + return severity; + } + + public CodeScanAlertEntity setSeverity(Severity severity) { + this.severity = severity; + return this; + } + + public String getFile() { + return file; + } + + public CodeScanAlertEntity setFile(String file) { + this.file = file; + return this; + } + + public Integer getLine() { + return line; + } + + public CodeScanAlertEntity setLine(Integer line) { + this.line = line; + return this; + } + + public String getBranch() { + return branch; + } + + public CodeScanAlertEntity setBranch(String branch) { + this.branch = branch; + return this; + } + + public Status getStatus() { + return status; + } + + public CodeScanAlertEntity setStatus(Status status) { + this.status = status; + return this; + } + + public String getTool() { + return tool; + } + + public CodeScanAlertEntity setTool(String tool) { + this.tool = tool; + return this; + } + + public OffsetDateTime getDetectedAt() { + return detectedAt; + } + + public CodeScanAlertEntity setDetectedAt(OffsetDateTime detectedAt) { + this.detectedAt = detectedAt; + return this; + } + + public String getDescription() { + return description; + } + + public CodeScanAlertEntity setDescription(String description) { + this.description = description; + return this; + } +} diff --git a/backend/libs/persistence/src/main/java/dev/cleat/persistence/entity/VulnerabilityEntity.java b/backend/libs/persistence/src/main/java/dev/cleat/persistence/entity/VulnerabilityEntity.java index a8873271..4d8d16e8 100644 --- a/backend/libs/persistence/src/main/java/dev/cleat/persistence/entity/VulnerabilityEntity.java +++ b/backend/libs/persistence/src/main/java/dev/cleat/persistence/entity/VulnerabilityEntity.java @@ -1,5 +1,6 @@ package dev.cleat.persistence.entity; +import dev.cleat.common.enums.Priority; import dev.cleat.common.enums.Reachable; import dev.cleat.common.enums.Severity; import jakarta.persistence.CollectionTable; @@ -47,6 +48,10 @@ public class VulnerabilityEntity { @Column(name = "severity") private Severity severity; + @Enumerated(EnumType.STRING) + @Column(name = "priority") + private Priority priority; + @Column(name = "epss") private Double epss; @@ -88,6 +93,7 @@ public VulnerabilityEntity( String fixedVersion, Double cvss, Severity severity, + Priority priority, Double epss, Boolean kev, Reachable reachable, @@ -105,6 +111,7 @@ public VulnerabilityEntity( this.fixedVersion = fixedVersion; this.cvss = cvss; this.severity = severity; + this.priority = priority; this.epss = epss; this.kev = kev; this.reachable = reachable; @@ -188,6 +195,15 @@ public VulnerabilityEntity setSeverity(Severity severity) { return this; } + public Priority getPriority() { + return priority; + } + + public VulnerabilityEntity setPriority(Priority priority) { + this.priority = priority; + return this; + } + public Double getEpss() { return epss; } diff --git a/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/AccountMapper.java b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/AccountMapper.java new file mode 100644 index 00000000..7a003042 --- /dev/null +++ b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/AccountMapper.java @@ -0,0 +1,38 @@ +package dev.cleat.persistence.mapper; + +import dev.cleat.common.dto.request.AccountRequestDto; +import dev.cleat.common.dto.response.AccountResponseDto; +import dev.cleat.persistence.entity.AccountEntity; +import org.springframework.stereotype.Component; + +@Component +public class AccountMapper { + public AccountResponseDto toAccountDto(AccountEntity accountEntity) { + if (accountEntity == null) { + return null; + } + + return new AccountResponseDto() + .setId(accountEntity.getId()) + .setLogin(accountEntity.getLogin()) + .setName(accountEntity.getName()) + .setType(accountEntity.getType()) + .setPlan(accountEntity.getPlan()) + .setRepoCount(accountEntity.getRepoCount()) + .setMemberCount(accountEntity.getMemberCount()) + .setPostureScore(accountEntity.getPostureScore()) + .setMonthlySpend(accountEntity.getMonthlySpend()) + .setReclaimable(accountEntity.getReclaimable()); + } + + public AccountEntity toAccountEntity(AccountRequestDto accountRequestDto) { + if (accountRequestDto == null) { + return null; + } + return new AccountEntity() + .setLogin(accountRequestDto.getLogin()) + .setName(accountRequestDto.getName()) + .setType(accountRequestDto.getType()) + .setPlan(accountRequestDto.getPlan()); + } +} diff --git a/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/ActivityEventMapper.java b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/ActivityEventMapper.java new file mode 100644 index 00000000..4c509778 --- /dev/null +++ b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/ActivityEventMapper.java @@ -0,0 +1,38 @@ +package dev.cleat.persistence.mapper; + +import dev.cleat.common.dto.request.ActivityEventRequestDto; +import dev.cleat.common.dto.response.ActivityEventResponseDto; +import dev.cleat.persistence.entity.ActivityEventEntity; +import org.springframework.stereotype.Component; + +@Component +public class ActivityEventMapper { + + public ActivityEventResponseDto toActivityEventDto(ActivityEventEntity activityEventEntity) { + if (activityEventEntity == null) { + return null; + } + return new ActivityEventResponseDto() + .setId(activityEventEntity.getId()) + .setAccountId(activityEventEntity.getAccountId()) + .setType(activityEventEntity.getType()) + .setSeverity(activityEventEntity.getSeverity()) + .setActor(activityEventEntity.getActor()) + .setTarget(activityEventEntity.getTarget()) + .setRepo(activityEventEntity.getRepo().getName()) + .setMessage(activityEventEntity.getMessage()) + .setCreatedAt(activityEventEntity.getCreatedAt()); + } + + public ActivityEventEntity toActiveEventEntity(ActivityEventRequestDto activityEventRequestDto) { + if (activityEventRequestDto == null) { + return null; + } + return new ActivityEventEntity() + .setType(activityEventRequestDto.getType()) + .setSeverity(activityEventRequestDto.getSeverity()) + .setActor(activityEventRequestDto.getActor()) + .setTarget(activityEventRequestDto.getTarget()) + .setMessage(activityEventRequestDto.getMessage()); + } +} diff --git a/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/CleatMapper.java b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/CleatMapper.java deleted file mode 100644 index 5e1ac1e2..00000000 --- a/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/CleatMapper.java +++ /dev/null @@ -1,292 +0,0 @@ -package dev.cleat.persistence.mapper; - -import dev.cleat.common.dto.UsagePointDto; -import dev.cleat.common.dto.request.AccountRequestDto; -import dev.cleat.common.dto.request.ActivityEventRequestDto; -import dev.cleat.common.dto.request.MemberRequestDto; -import dev.cleat.common.dto.request.RepoRequestDto; -import dev.cleat.common.dto.request.SecretFindingRequestDto; -import dev.cleat.common.dto.request.VulnerabilityRequestDto; -import dev.cleat.common.dto.response.AccountResponseDto; -import dev.cleat.common.dto.response.ActivityEventResponseDto; -import dev.cleat.common.dto.response.DatasetDto; -import dev.cleat.common.dto.response.MemberResponseDto; -import dev.cleat.common.dto.response.RepoResponseDto; -import dev.cleat.common.dto.response.ScorecardCheckResponseDto; -import dev.cleat.common.dto.response.SecretFindingResponseDto; -import dev.cleat.common.dto.response.UsageResponseDto; -import dev.cleat.common.dto.response.VulnerabilityResponseDto; -import dev.cleat.persistence.entity.AccountEntity; -import dev.cleat.persistence.entity.ActivityEventEntity; -import dev.cleat.persistence.entity.MemberEntity; -import dev.cleat.persistence.entity.RepoEntity; -import dev.cleat.persistence.entity.ScorecardCheckEntity; -import dev.cleat.persistence.entity.SecretFindingEntity; -import dev.cleat.persistence.entity.UsageEntity; -import dev.cleat.persistence.entity.UsagePointEntity; -import dev.cleat.persistence.entity.VulnerabilityEntity; -import java.util.List; -import org.springframework.stereotype.Component; - -@Component -public class CleatMapper { - - public AccountResponseDto toAccountDto(AccountEntity accountEntity) { - if (accountEntity == null) { - return null; - } - - return new AccountResponseDto() - .setId(accountEntity.getId()) - .setLogin(accountEntity.getLogin()) - .setName(accountEntity.getName()) - .setType(accountEntity.getType()) - .setPlan(accountEntity.getPlan()) - .setRepoCount(accountEntity.getRepoCount()) - .setMemberCount(accountEntity.getMemberCount()) - .setPostureScore(accountEntity.getPostureScore()) - .setMonthlySpend(accountEntity.getMonthlySpend()) - .setReclaimable(accountEntity.getReclaimable()); - } - - public AccountEntity toAccountEntity(AccountRequestDto accountRequestDto) { - if (accountRequestDto == null) { - return null; - } - return new AccountEntity() - .setLogin(accountRequestDto.getLogin()) - .setName(accountRequestDto.getName()) - .setType(accountRequestDto.getType()) - .setPlan(accountRequestDto.getPlan()); - } - - public RepoResponseDto toRepoDto(RepoEntity repoEntity) { - if (repoEntity == null) { - return null; - } - return new RepoResponseDto() - .setId(repoEntity.getId()) - .setName(repoEntity.getName()) - .setAccountId(repoEntity.getAccount().getId()) - .setVisibility(repoEntity.getVisibility()) - .setLanguage(repoEntity.getLanguage()) - .setStars(repoEntity.getStars()) - .setDefaultBranch(repoEntity.getDefaultBranch()) - .setBranchProtected(repoEntity.getBranchProtected()) - .setHasReadme(repoEntity.getHasReadme()) - .setHasLicense(repoEntity.getHasLicense()) - .setHasContributing(repoEntity.getHasContributing()) - .setHasCodeowners(repoEntity.getHasCodeowners()) - .setHasCI(repoEntity.getHasCi()) - .setSizeMb(repoEntity.getSizeMb()) - .setLastPushedAt(repoEntity.getLastPushedAt()) - .setArchived(repoEntity.getArchived()) - .setOpenVulns(repoEntity.getOpenVulns()) - .setOpenSecrets(repoEntity.getOpenSecrets()) - .setOpenCodeAlerts(repoEntity.getOpenCodeAlerts()) - .setStaleBranches(repoEntity.getStaleBranches()) - .setOpenPRs(repoEntity.getOpenPRs()) - .setHygieneScore(repoEntity.getHygieneScore()) - .setScorecard((repoEntity.getScorecard()) - .stream().map(this::toScorecardCheckResponseDto).toList()) - .setTopics(repoEntity.getTopics()); - } - - public RepoEntity toRepoEntity(RepoRequestDto repoRequestDto) { - if (repoRequestDto == null) { - return null; - } - return new RepoEntity() - .setName(repoRequestDto.getName()) - .setVisibility(repoRequestDto.getVisibility()) - .setLanguage(repoRequestDto.getLanguage()) - .setDefaultBranch(repoRequestDto.getDefaultBranch()) - .setTopics(repoRequestDto.getTopics()); - } - - public ScorecardCheckResponseDto toScorecardCheckResponseDto(ScorecardCheckEntity scorecardCheckEntity) { - if (scorecardCheckEntity == null) { - return null; - } - return new ScorecardCheckResponseDto() - .setId(scorecardCheckEntity.getId()) - .setName(scorecardCheckEntity.getName()) - .setReason(scorecardCheckEntity.getReason()) - .setScore(scorecardCheckEntity.getScore()); - } - - public ActivityEventResponseDto toActivityEventDto(ActivityEventEntity activityEventEntity) { - if (activityEventEntity == null) { - return null; - } - return new ActivityEventResponseDto() - .setId(activityEventEntity.getId()) - .setAccountId(activityEventEntity.getAccountId()) - .setType(activityEventEntity.getType()) - .setSeverity(activityEventEntity.getSeverity()) - .setActor(activityEventEntity.getActor()) - .setTarget(activityEventEntity.getTarget()) - .setRepo(activityEventEntity.getRepo().getName()) - .setMessage(activityEventEntity.getMessage()) - .setCreatedAt(activityEventEntity.getCreatedAt()); - } - - public ActivityEventEntity toActiveEventEntity(ActivityEventRequestDto activityEventRequestDto) { - if (activityEventRequestDto == null) { - return null; - } - return new ActivityEventEntity() - .setType(activityEventRequestDto.getType()) - .setSeverity(activityEventRequestDto.getSeverity()) - .setActor(activityEventRequestDto.getActor()) - .setTarget(activityEventRequestDto.getTarget()) - .setMessage(activityEventRequestDto.getMessage()); - } - - public DatasetDto toDatasetDto( - AccountResponseDto account, - List repos, - List secrets, - List vulnerabilities, - UsageResponseDto usage, - List members, - List events) { - return new DatasetDto() - .setAccount(account) - .setRepos(repos) - .setSecrets(secrets) - .setVulnerabilities(vulnerabilities) - .setUsage(usage) - .setMembers(members) - .setEvents(events); - } - - public MemberResponseDto toMemberDto(MemberEntity memberEntity) { - return new MemberResponseDto() - .setId(memberEntity.getId()) - .setLogin(memberEntity.getLogin()) - .setName(memberEntity.getName()) - .setRole(memberEntity.getRole()) - .setTwoFactor(memberEntity.getTwoFactor()) - .setLastActiveAt(memberEntity.getLastActiveAt()) - .setTeams(memberEntity.getTeams()) - .setOutsideCollaborator(memberEntity.getOutsideCollaborator()) - .setRepoAccess(memberEntity.getRepoAccess()); - } - - public MemberEntity toMemberEntity(MemberRequestDto memberRequestDto) { - if (memberRequestDto == null) { - return null; - } - return new MemberEntity() - .setLogin(memberRequestDto.getLogin()) - .setName(memberRequestDto.getName()) - .setRole(memberRequestDto.getRole()) - .setTwoFactor(memberRequestDto.getTwoFactor()) - .setTeams(memberRequestDto.getTeams()); - } - - public SecretFindingResponseDto toSecretFindingDto(SecretFindingEntity secretFindingEntity) { - return new SecretFindingResponseDto() - .setId(secretFindingEntity.getId()) - .setAccountId(secretFindingEntity.getAccountId()) - .setRepo(secretFindingEntity.getRepo().getName()) - .setProvider(secretFindingEntity.getProvider()) - .setSecretType(secretFindingEntity.getSecretType()) - .setFile(secretFindingEntity.getFile()) - .setLine(secretFindingEntity.getLine()) - .setCommit(secretFindingEntity.getCommit()) - .setAuthor(secretFindingEntity.getAuthor()) - .setDetectedAt(secretFindingEntity.getDetectedAt()) - .setValidity(secretFindingEntity.getValidity()) - .setSeverity(secretFindingEntity.getSeverity()) - .setPushProtectionBlocked(secretFindingEntity.getPushProtectionBlocked()); - } - - public SecretFindingEntity toSecretFindingEntity(SecretFindingRequestDto secretFindingRequestDto) { - if (secretFindingRequestDto == null) { - return null; - } - return new SecretFindingEntity() - .setProvider(secretFindingRequestDto.getProvider()) - .setSecretType(secretFindingRequestDto.getSecretType()) - .setFile(secretFindingRequestDto.getFile()) - .setLine(secretFindingRequestDto.getLine()) - .setCommit(secretFindingRequestDto.getCommit()) - .setAuthor(secretFindingRequestDto.getAuthor()) - .setDetectedAt(secretFindingRequestDto.getDetectedAt()) - .setValidity(secretFindingRequestDto.getValidity()) - .setSeverity(secretFindingRequestDto.getSeverity()) - .setPushProtectionBlocked(secretFindingRequestDto.getPushProtectionBlocked()); - } - - public VulnerabilityResponseDto toVulnerabilityDto(VulnerabilityEntity vulnerabilityEntity) { - return new VulnerabilityResponseDto() - .setId(vulnerabilityEntity.getId()) - .setAccountId(vulnerabilityEntity.getAccountId()) - .setPackageName(vulnerabilityEntity.getPackageName()) - .setEcosystem(vulnerabilityEntity.getEcosystem()) - .setCurrentVersion(vulnerabilityEntity.getCurrentVersion()) - .setFixedVersion(vulnerabilityEntity.getFixedVersion()) - .setCvss(vulnerabilityEntity.getCvss()) - .setSeverity(vulnerabilityEntity.getSeverity()) - .setEpss(vulnerabilityEntity.getEpss()) - .setKev(vulnerabilityEntity.getKev()) - .setReachable(vulnerabilityEntity.getReachable()) - .setAdvisoryId(vulnerabilityEntity.getAdvisoryId()) - .setCwe(vulnerabilityEntity.getCwe()) - .setTitle(vulnerabilityEntity.getTitle()) - .setAffectedRepos(vulnerabilityEntity.getAffectedRepos()) - .setHasFixPr(vulnerabilityEntity.getHasFixPr()) - .setPublishedAt(vulnerabilityEntity.getPublishedAt()); - } - - public VulnerabilityEntity toVulnerabilityEntity(VulnerabilityRequestDto vulnerabilityRequestDto) { - if (vulnerabilityRequestDto == null) { - return null; - } - return new VulnerabilityEntity() - .setPackageName(vulnerabilityRequestDto.getPackageName()) - .setEcosystem(vulnerabilityRequestDto.getEcosystem()) - .setCurrentVersion(vulnerabilityRequestDto.getCurrentVersion()) - .setFixedVersion(vulnerabilityRequestDto.getFixedVersion()) - .setCvss(vulnerabilityRequestDto.getCvss()) - .setSeverity(vulnerabilityRequestDto.getSeverity()) - .setEpss(vulnerabilityRequestDto.getEpss()) - .setKev(vulnerabilityRequestDto.getKev()) - .setReachable(vulnerabilityRequestDto.getReachable()) - .setAdvisoryId(vulnerabilityRequestDto.getAdvisoryId()) - .setCwe(vulnerabilityRequestDto.getCwe()) - .setTitle(vulnerabilityRequestDto.getTitle()) - .setAffectedRepos(vulnerabilityRequestDto.getAffectedRepos()) - .setHasFixPr(vulnerabilityRequestDto.getHasFixPr()); - } - - public UsageResponseDto toUsageDto(UsageEntity usageEntity) { - if (usageEntity == null) { - return null; - } - return new UsageResponseDto() - .setActionsMinutes(usageEntity.getActionsMinutes()) - .setMinutesIncluded(usageEntity.getMinutesIncluded()) - .setStorageGb(usageEntity.getStorageGb()) - .setStorageIncludedGb(usageEntity.getStorageIncludedGb()) - .setMonthlyCost(usageEntity.getMonthlyCost()) - .setReclaimable(usageEntity.getReclaimable()) - .setBreakdown(usageEntity.getBreakdown()) - .setSeries(usageEntity.getSeries().stream() - .map(this::toUsagePointDto) - .toList()); - } - - public UsagePointDto toUsagePointDto(UsagePointEntity usagePointEntity) { - if (usagePointEntity == null) { - return null; - } - return new UsagePointDto() - .setLabel(usagePointEntity.getLabel()) - .setMinutes(usagePointEntity.getMinutes()) - .setStorageGb(usagePointEntity.getStorageGb()) - .setCost(usagePointEntity.getCost()); - } -} diff --git a/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/CodeScanAlertMapper.java b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/CodeScanAlertMapper.java new file mode 100644 index 00000000..c50f2704 --- /dev/null +++ b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/CodeScanAlertMapper.java @@ -0,0 +1,50 @@ +package dev.cleat.persistence.mapper; + +import dev.cleat.common.dto.request.CodeScanAlertRequestDto; +import dev.cleat.common.dto.response.CodeScanAlertResponseDto; +import dev.cleat.persistence.entity.CodeScanAlertEntity; +import org.springframework.stereotype.Component; + +@Component +public class CodeScanAlertMapper { + + public CodeScanAlertResponseDto toCodeScanAlertDto(CodeScanAlertEntity codeScanAlertEntity) { + + if (codeScanAlertEntity == null) { + return null; + } + return new CodeScanAlertResponseDto( + codeScanAlertEntity.getId(), + codeScanAlertEntity.getAccountId(), + codeScanAlertEntity.getRepo(), + codeScanAlertEntity.getRule(), + codeScanAlertEntity.getRuleId(), + codeScanAlertEntity.getSeverity(), + codeScanAlertEntity.getFile(), + codeScanAlertEntity.getLine(), + codeScanAlertEntity.getBranch(), + codeScanAlertEntity.getStatus(), + codeScanAlertEntity.getTool(), + codeScanAlertEntity.getDetectedAt(), + codeScanAlertEntity.getDescription()); + } + + public CodeScanAlertEntity toCodeScanAlertEntity(CodeScanAlertRequestDto codeScanAlertRequestDto) { + + if (codeScanAlertRequestDto == null) { + return null; + } + + return new CodeScanAlertEntity() + .setAccountId(codeScanAlertRequestDto.getAccountId()) + .setRepo(codeScanAlertRequestDto.getRepo()) + .setRule(codeScanAlertRequestDto.getRule()) + .setRuleId(codeScanAlertRequestDto.getRuleId()) + .setSeverity(codeScanAlertRequestDto.getSeverity()) + .setFile(codeScanAlertRequestDto.getFile()) + .setLine(codeScanAlertRequestDto.getLine()) + .setBranch(codeScanAlertRequestDto.getBranch()) + .setTool(codeScanAlertRequestDto.getTool()) + .setDescription(codeScanAlertRequestDto.getDescription()); + } +} diff --git a/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/MemberMapper.java b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/MemberMapper.java new file mode 100644 index 00000000..5e1cea74 --- /dev/null +++ b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/MemberMapper.java @@ -0,0 +1,35 @@ +package dev.cleat.persistence.mapper; + +import dev.cleat.common.dto.request.MemberRequestDto; +import dev.cleat.common.dto.response.MemberResponseDto; +import dev.cleat.persistence.entity.MemberEntity; +import org.springframework.stereotype.Component; + +@Component +public class MemberMapper { + + public MemberResponseDto toMemberDto(MemberEntity memberEntity) { + return new MemberResponseDto() + .setId(memberEntity.getId()) + .setLogin(memberEntity.getLogin()) + .setName(memberEntity.getName()) + .setRole(memberEntity.getRole()) + .setTwoFactor(memberEntity.getTwoFactor()) + .setLastActiveAt(memberEntity.getLastActiveAt()) + .setTeams(memberEntity.getTeams()) + .setOutsideCollaborator(memberEntity.getOutsideCollaborator()) + .setRepoAccess(memberEntity.getRepoAccess()); + } + + public MemberEntity toMemberEntity(MemberRequestDto memberRequestDto) { + if (memberRequestDto == null) { + return null; + } + return new MemberEntity() + .setLogin(memberRequestDto.getLogin()) + .setName(memberRequestDto.getName()) + .setRole(memberRequestDto.getRole()) + .setTwoFactor(memberRequestDto.getTwoFactor()) + .setTeams(memberRequestDto.getTeams()); + } +} diff --git a/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/RepoMapper.java b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/RepoMapper.java new file mode 100644 index 00000000..c547df20 --- /dev/null +++ b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/RepoMapper.java @@ -0,0 +1,62 @@ +package dev.cleat.persistence.mapper; + +import dev.cleat.common.dto.request.RepoRequestDto; +import dev.cleat.common.dto.response.RepoResponseDto; +import dev.cleat.persistence.entity.RepoEntity; +import org.springframework.stereotype.Component; + +@Component +public class RepoMapper { + + private final ScorecardCheckMapper scorecardCheckMapper; + + public RepoMapper(ScorecardCheckMapper scorecardCheckMapper) { + this.scorecardCheckMapper = scorecardCheckMapper; + } + + public RepoResponseDto toRepoDto(RepoEntity repoEntity) { + if (repoEntity == null) { + return null; + } + return new RepoResponseDto() + .setId(repoEntity.getId()) + .setName(repoEntity.getName()) + .setAccountId(repoEntity.getAccount().getId()) + .setVisibility(repoEntity.getVisibility()) + .setLanguage(repoEntity.getLanguage()) + .setStars(repoEntity.getStars()) + .setDefaultBranch(repoEntity.getDefaultBranch()) + .setBranchProtected(repoEntity.getBranchProtected()) + .setHasReadme(repoEntity.getHasReadme()) + .setHasLicense(repoEntity.getHasLicense()) + .setHasContributing(repoEntity.getHasContributing()) + .setHasCodeowners(repoEntity.getHasCodeowners()) + .setHasCI(repoEntity.getHasCi()) + .setSizeMb(repoEntity.getSizeMb()) + .setLastPushedAt(repoEntity.getLastPushedAt()) + .setArchived(repoEntity.getArchived()) + .setOpenVulns(repoEntity.getOpenVulns()) + .setOpenSecrets(repoEntity.getOpenSecrets()) + .setOpenCodeAlerts(repoEntity.getOpenCodeAlerts()) + .setStaleBranches(repoEntity.getStaleBranches()) + .setOpenPRs(repoEntity.getOpenPRs()) + .setHygieneScore(repoEntity.getHygieneScore()) + .setScorecard((repoEntity.getScorecard()) + .stream() + .map(scorecardCheckMapper::toScorecardCheckResponseDto) + .toList()) + .setTopics(repoEntity.getTopics()); + } + + public RepoEntity toRepoEntity(RepoRequestDto repoRequestDto) { + if (repoRequestDto == null) { + return null; + } + return new RepoEntity() + .setName(repoRequestDto.getName()) + .setVisibility(repoRequestDto.getVisibility()) + .setLanguage(repoRequestDto.getLanguage()) + .setDefaultBranch(repoRequestDto.getDefaultBranch()) + .setTopics(repoRequestDto.getTopics()); + } +} diff --git a/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/ScorecardCheckMapper.java b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/ScorecardCheckMapper.java new file mode 100644 index 00000000..8ae2b139 --- /dev/null +++ b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/ScorecardCheckMapper.java @@ -0,0 +1,20 @@ +package dev.cleat.persistence.mapper; + +import dev.cleat.common.dto.response.ScorecardCheckResponseDto; +import dev.cleat.persistence.entity.ScorecardCheckEntity; +import org.springframework.stereotype.Component; + +@Component +public class ScorecardCheckMapper { + + public ScorecardCheckResponseDto toScorecardCheckResponseDto(ScorecardCheckEntity scorecardCheckEntity) { + if (scorecardCheckEntity == null) { + return null; + } + return new ScorecardCheckResponseDto() + .setId(scorecardCheckEntity.getId()) + .setName(scorecardCheckEntity.getName()) + .setReason(scorecardCheckEntity.getReason()) + .setScore(scorecardCheckEntity.getScore()); + } +} diff --git a/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/SecretFindingMapper.java b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/SecretFindingMapper.java new file mode 100644 index 00000000..b0fb8de0 --- /dev/null +++ b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/SecretFindingMapper.java @@ -0,0 +1,44 @@ +package dev.cleat.persistence.mapper; + +import dev.cleat.common.dto.request.SecretFindingRequestDto; +import dev.cleat.common.dto.response.SecretFindingResponseDto; +import dev.cleat.persistence.entity.SecretFindingEntity; +import org.springframework.stereotype.Component; + +@Component +public class SecretFindingMapper { + + public SecretFindingResponseDto toSecretFindingDto(SecretFindingEntity secretFindingEntity) { + return new SecretFindingResponseDto() + .setId(secretFindingEntity.getId()) + .setAccountId(secretFindingEntity.getAccountId()) + .setRepo(secretFindingEntity.getRepo().getName()) + .setProvider(secretFindingEntity.getProvider()) + .setSecretType(secretFindingEntity.getSecretType()) + .setFile(secretFindingEntity.getFile()) + .setLine(secretFindingEntity.getLine()) + .setCommit(secretFindingEntity.getCommit()) + .setAuthor(secretFindingEntity.getAuthor()) + .setDetectedAt(secretFindingEntity.getDetectedAt()) + .setValidity(secretFindingEntity.getValidity()) + .setSeverity(secretFindingEntity.getSeverity()) + .setPushProtectionBlocked(secretFindingEntity.getPushProtectionBlocked()); + } + + public SecretFindingEntity toSecretFindingEntity(SecretFindingRequestDto secretFindingRequestDto) { + if (secretFindingRequestDto == null) { + return null; + } + return new SecretFindingEntity() + .setProvider(secretFindingRequestDto.getProvider()) + .setSecretType(secretFindingRequestDto.getSecretType()) + .setFile(secretFindingRequestDto.getFile()) + .setLine(secretFindingRequestDto.getLine()) + .setCommit(secretFindingRequestDto.getCommit()) + .setAuthor(secretFindingRequestDto.getAuthor()) + .setDetectedAt(secretFindingRequestDto.getDetectedAt()) + .setValidity(secretFindingRequestDto.getValidity()) + .setSeverity(secretFindingRequestDto.getSeverity()) + .setPushProtectionBlocked(secretFindingRequestDto.getPushProtectionBlocked()); + } +} diff --git a/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/UsageMapper.java b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/UsageMapper.java new file mode 100644 index 00000000..1f836dad --- /dev/null +++ b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/UsageMapper.java @@ -0,0 +1,32 @@ +package dev.cleat.persistence.mapper; + +import dev.cleat.common.dto.response.UsageResponseDto; +import dev.cleat.persistence.entity.UsageEntity; +import org.springframework.stereotype.Component; + +@Component +public class UsageMapper { + + private final UsagePointMapper usagePointMapper; + + public UsageMapper(UsagePointMapper usagePointMapper) { + this.usagePointMapper = usagePointMapper; + } + + public UsageResponseDto toUsageDto(UsageEntity usageEntity) { + if (usageEntity == null) { + return null; + } + return new UsageResponseDto() + .setActionsMinutes(usageEntity.getActionsMinutes()) + .setMinutesIncluded(usageEntity.getMinutesIncluded()) + .setStorageGb(usageEntity.getStorageGb()) + .setStorageIncludedGb(usageEntity.getStorageIncludedGb()) + .setMonthlyCost(usageEntity.getMonthlyCost()) + .setReclaimable(usageEntity.getReclaimable()) + .setBreakdown(usageEntity.getBreakdown()) + .setSeries(usageEntity.getSeries().stream() + .map(usagePointMapper::toUsagePointDto) + .toList()); + } +} diff --git a/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/UsagePointMapper.java b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/UsagePointMapper.java new file mode 100644 index 00000000..4a68ea7b --- /dev/null +++ b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/UsagePointMapper.java @@ -0,0 +1,20 @@ +package dev.cleat.persistence.mapper; + +import dev.cleat.common.dto.UsagePointDto; +import dev.cleat.persistence.entity.UsagePointEntity; +import org.springframework.stereotype.Component; + +@Component +public class UsagePointMapper { + + public UsagePointDto toUsagePointDto(UsagePointEntity usagePointEntity) { + if (usagePointEntity == null) { + return null; + } + return new UsagePointDto() + .setLabel(usagePointEntity.getLabel()) + .setMinutes(usagePointEntity.getMinutes()) + .setStorageGb(usagePointEntity.getStorageGb()) + .setCost(usagePointEntity.getCost()); + } +} diff --git a/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/VulnerabilityMapper.java b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/VulnerabilityMapper.java new file mode 100644 index 00000000..dc8f8d0a --- /dev/null +++ b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/VulnerabilityMapper.java @@ -0,0 +1,53 @@ +package dev.cleat.persistence.mapper; + +import dev.cleat.common.dto.request.VulnerabilityRequestDto; +import dev.cleat.common.dto.response.VulnerabilityResponseDto; +import dev.cleat.persistence.entity.VulnerabilityEntity; +import org.springframework.stereotype.Component; + +@Component +public class VulnerabilityMapper { + public VulnerabilityResponseDto toVulnerabilityDto(VulnerabilityEntity vulnerabilityEntity) { + return new VulnerabilityResponseDto() + .setId(vulnerabilityEntity.getId()) + .setAccountId(vulnerabilityEntity.getAccountId()) + .setPackageName(vulnerabilityEntity.getPackageName()) + .setEcosystem(vulnerabilityEntity.getEcosystem()) + .setCurrentVersion(vulnerabilityEntity.getCurrentVersion()) + .setFixedVersion(vulnerabilityEntity.getFixedVersion()) + .setCvss(vulnerabilityEntity.getCvss()) + .setSeverity(vulnerabilityEntity.getSeverity()) + .setPriority(vulnerabilityEntity.getPriority()) + .setEpss(vulnerabilityEntity.getEpss()) + .setKev(vulnerabilityEntity.getKev()) + .setReachable(vulnerabilityEntity.getReachable()) + .setAdvisoryId(vulnerabilityEntity.getAdvisoryId()) + .setCwe(vulnerabilityEntity.getCwe()) + .setTitle(vulnerabilityEntity.getTitle()) + .setAffectedRepos(vulnerabilityEntity.getAffectedRepos()) + .setHasFixPr(vulnerabilityEntity.getHasFixPr()) + .setPublishedAt(vulnerabilityEntity.getPublishedAt()); + } + + public VulnerabilityEntity toVulnerabilityEntity(VulnerabilityRequestDto vulnerabilityRequestDto) { + if (vulnerabilityRequestDto == null) { + return null; + } + return new VulnerabilityEntity() + .setPackageName(vulnerabilityRequestDto.getPackageName()) + .setEcosystem(vulnerabilityRequestDto.getEcosystem()) + .setCurrentVersion(vulnerabilityRequestDto.getCurrentVersion()) + .setFixedVersion(vulnerabilityRequestDto.getFixedVersion()) + .setCvss(vulnerabilityRequestDto.getCvss()) + .setSeverity(vulnerabilityRequestDto.getSeverity()) + .setPriority(vulnerabilityRequestDto.getPriority()) + .setEpss(vulnerabilityRequestDto.getEpss()) + .setKev(vulnerabilityRequestDto.getKev()) + .setReachable(vulnerabilityRequestDto.getReachable()) + .setAdvisoryId(vulnerabilityRequestDto.getAdvisoryId()) + .setCwe(vulnerabilityRequestDto.getCwe()) + .setTitle(vulnerabilityRequestDto.getTitle()) + .setAffectedRepos(vulnerabilityRequestDto.getAffectedRepos()) + .setHasFixPr(vulnerabilityRequestDto.getHasFixPr()); + } +} diff --git a/backend/libs/persistence/src/main/java/dev/cleat/persistence/repository/CodeScanAlertRepository.java b/backend/libs/persistence/src/main/java/dev/cleat/persistence/repository/CodeScanAlertRepository.java new file mode 100644 index 00000000..6ad285cd --- /dev/null +++ b/backend/libs/persistence/src/main/java/dev/cleat/persistence/repository/CodeScanAlertRepository.java @@ -0,0 +1,11 @@ +package dev.cleat.persistence.repository; + +import dev.cleat.persistence.entity.CodeScanAlertEntity; +import java.util.List; +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CodeScanAlertRepository extends JpaRepository { + + List findAllByAccountId(UUID accountId); +} diff --git a/backend/libs/persistence/src/main/resources/db/migration/V2__update_repo_and_add_tables.sql b/backend/libs/persistence/src/main/resources/db/migration/V2__update_repo_and_add_tables.sql index 93df4f95..971a811a 100644 --- a/backend/libs/persistence/src/main/resources/db/migration/V2__update_repo_and_add_tables.sql +++ b/backend/libs/persistence/src/main/resources/db/migration/V2__update_repo_and_add_tables.sql @@ -160,4 +160,30 @@ CREATE TABLE vulnerability_affected_repos( ); +CREATE INDEX idx_scorecard_repo_id on scorecard_check(repo_id); + +CREATE INDEX idx_repo_topics_repo_id on repo_topics(repo_id); + +CREATE INDEX idx_activity_account_id on activity_event(account_id); +CREATE INDEX idx_activity_repo_id on activity_event(repo); +CREATE INDEX idx_activity_created_at on activity_event(created_at); + +CREATE INDEX idx_member_account_id on member(account_id); +CREATE INDEX idx_member_login on member(login); + +CREATE INDEX idx_secret_account_id on secret_finding(account_id); +CREATE INDEX idx_secret_repo_id on secret_finding(repo); +CREATE INDEX idx_secret_severity on secret_finding(severity); + +CREATE INDEX idx_usage_account_id on usage(account_id); + +CREATE INDEX idx_usage_point_usage_id on usage_point(usage_id); + +CREATE INDEX idx_vulnerability_account_id on vulnerability(account_id); +CREATE INDEX idx_vulnerability_severity on vulnerability(severity); +CREATE INDEX idx_vulnerability_kev on vulnerability(kev); +CREATE INDEX idx_vulnerability_cvss on vulnerability(cvss); + +CREATE INDEX idx_vulnerability_affected_repos_id on vulnerability_affected_repos(vulnerability_id); + diff --git a/backend/libs/persistence/src/main/resources/db/migration/V3_alter_vulnerability_table.sql b/backend/libs/persistence/src/main/resources/db/migration/V3_alter_vulnerability_table.sql new file mode 100644 index 00000000..9a165474 --- /dev/null +++ b/backend/libs/persistence/src/main/resources/db/migration/V3_alter_vulnerability_table.sql @@ -0,0 +1 @@ +ALTER TABLE vulnerability ADD COLUMN priority VARCHAR(50 ); diff --git a/backend/libs/persistence/src/main/resources/db/migration/V4__create_code_scan_alerts_table.sql b/backend/libs/persistence/src/main/resources/db/migration/V4__create_code_scan_alerts_table.sql new file mode 100644 index 00000000..16636319 --- /dev/null +++ b/backend/libs/persistence/src/main/resources/db/migration/V4__create_code_scan_alerts_table.sql @@ -0,0 +1,37 @@ +CREATE TABLE code_scan_alerts( + id UUID PRIMARY KEY, + account_id UUID NOT NULL, + severity VARCHAR(50) NOT NULL, + file VARCHAR(255) NOT NULL, + line INTEGER CHECK (line>=0), + branch VARCHAR(250) DEFAULT 'main', + status VARCHAR(50) NOT NULL , + tool VARCHAR(255) NOT NULL, + detected_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + description TEXT, + repo UUID NOT NULL, + rule VARCHAR(250) NOT NULL, + rule_id UUID NOT NULL, + + CONSTRAINT fk_account + FOREIGN KEY(account_id) + REFERENCES account(id) + ON DELETE CASCADE, + + CONSTRAINT fk_repo + FOREIGN KEY(repo) + REFERENCES repo(id) + ON DELETE CASCADE, + + CONSTRAINT chk_severity + CHECK (severity + IN('CRITICAL','HIGH','MEDIUM','LOW')), + + CONSTRAINT chk_status + CHECK (status IN('OPEN','FIXED','DISMISSED')) + +); + +CREATE INDEX idx_scan_alerts_repo_severity on code_scan_alerts(repo,severity); +CREATE INDEX idx_scan_alerts_detected_at on code_scan_alerts(detected_at); + diff --git a/backend/libs/persistence/src/main/resources/db/migration/V5__alter_secret_finding_table.sql b/backend/libs/persistence/src/main/resources/db/migration/V5__alter_secret_finding_table.sql new file mode 100644 index 00000000..6464da71 --- /dev/null +++ b/backend/libs/persistence/src/main/resources/db/migration/V5__alter_secret_finding_table.sql @@ -0,0 +1,3 @@ +ALTER TABLE secret_finding +ALTER COLUMN detected_at + SET DEFAULT CURRENT_TIMESTAMP; diff --git a/backend/libs/scanning/build.gradle.kts b/backend/libs/scanning/build.gradle.kts index 0d4d4aca..41a5c34d 100644 --- a/backend/libs/scanning/build.gradle.kts +++ b/backend/libs/scanning/build.gradle.kts @@ -11,4 +11,9 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter") testImplementation("org.springframework.boot:spring-boot-starter-test") + + testRuntimeOnly("org.junit.platform:junit-platform-launcher") +} +tasks.test{ + useJUnitPlatform() } diff --git a/backend/libs/scanning/src/main/java/dev/cleat/scanning/CodeScanAlertScanner.java b/backend/libs/scanning/src/main/java/dev/cleat/scanning/CodeScanAlertScanner.java new file mode 100644 index 00000000..4d827af8 --- /dev/null +++ b/backend/libs/scanning/src/main/java/dev/cleat/scanning/CodeScanAlertScanner.java @@ -0,0 +1,31 @@ +package dev.cleat.scanning; + +import dev.cleat.common.enums.Severity; +import dev.cleat.common.enums.Status; +import dev.cleat.persistence.entity.CodeScanAlertEntity; +import dev.cleat.persistence.repository.CodeScanAlertRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +@Service +public class CodeScanAlertScanner { + private static final Logger log = LoggerFactory.getLogger(CodeScanAlertScanner.class); + private final CodeScanAlertRepository codeScanAlertRepository; + + public CodeScanAlertScanner(CodeScanAlertRepository codeScanAlertRepository) { + this.codeScanAlertRepository = codeScanAlertRepository; + } + + public void process(CodeScanAlertEntity codeScanAlertEntity) { + log.info("Analyzing code scan alert for rule: {}", codeScanAlertEntity.getRule()); + if (Severity.CRITICAL.equals(codeScanAlertEntity.getSeverity()) + || Severity.HIGH.equals(codeScanAlertEntity.getSeverity())) { + codeScanAlertEntity.setStatus(Status.OPEN); + } else { + codeScanAlertEntity.setStatus(Status.OPEN); + } + codeScanAlertRepository.save(codeScanAlertEntity); + log.info("Code scan alert processed successfully with status: {}", codeScanAlertEntity.getStatus()); + } +} diff --git a/backend/libs/scanning/src/main/java/dev/cleat/scanning/SecretFindingScanner.java b/backend/libs/scanning/src/main/java/dev/cleat/scanning/SecretFindingScanner.java new file mode 100644 index 00000000..9a8355fd --- /dev/null +++ b/backend/libs/scanning/src/main/java/dev/cleat/scanning/SecretFindingScanner.java @@ -0,0 +1,32 @@ +package dev.cleat.scanning; + +import dev.cleat.common.enums.Severity; +import dev.cleat.common.enums.Validity; +import dev.cleat.persistence.entity.SecretFindingEntity; +import dev.cleat.persistence.repository.SecretFindingRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +@Service +public class SecretFindingScanner { + private static final Logger log = LoggerFactory.getLogger(SecretFindingScanner.class); + private final SecretFindingRepository secretFindingRepository; + + public SecretFindingScanner(SecretFindingRepository secretFindingRepository) { + this.secretFindingRepository = secretFindingRepository; + } + + public void process(SecretFindingEntity secretFindingEntity) { + log.info( + "Processing secret finding for repo: {}", + secretFindingEntity.getRepo().getName()); + if (Boolean.FALSE.equals(secretFindingEntity.getPushProtectionBlocked())) { + secretFindingEntity.setSeverity(Severity.CRITICAL).setValidity(Validity.ACTIVE); + } else { + secretFindingEntity.setSeverity(Severity.LOW).setValidity(Validity.REVOKED); + } + secretFindingRepository.save(secretFindingEntity); + log.info("Secret finding processed successfully: {}", secretFindingEntity.getId()); + } +} diff --git a/backend/libs/scanning/src/main/java/dev/cleat/scanning/VulnerabilityScanner.java b/backend/libs/scanning/src/main/java/dev/cleat/scanning/VulnerabilityScanner.java new file mode 100644 index 00000000..33986562 --- /dev/null +++ b/backend/libs/scanning/src/main/java/dev/cleat/scanning/VulnerabilityScanner.java @@ -0,0 +1,40 @@ +package dev.cleat.scanning; + +import dev.cleat.domain.PriorityCalculator; +import dev.cleat.domain.model.Vulnerability; +import dev.cleat.persistence.entity.VulnerabilityEntity; +import dev.cleat.persistence.repository.VulnerabilityRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +@Service +public class VulnerabilityScanner { + private final PriorityCalculator priorityCalculator; + private final VulnerabilityRepository vulnerabilityRepository; + private static final Logger log = LoggerFactory.getLogger(VulnerabilityScanner.class); + + public VulnerabilityScanner( + PriorityCalculator priorityCalculator, VulnerabilityRepository vulnerabilityRepository) { + this.priorityCalculator = priorityCalculator; + this.vulnerabilityRepository = vulnerabilityRepository; + } + + public void processAndSave(VulnerabilityEntity vulnerabilityEntity) { + + try { + if (vulnerabilityEntity == null) { + throw new IllegalArgumentException("VulnerabilityEntity cannot be null"); + } + + Vulnerability vulnerability = new Vulnerability( + vulnerabilityEntity.getKev(), vulnerabilityEntity.getCvss(), vulnerabilityEntity.getSeverity()); + vulnerabilityEntity.setPriority(priorityCalculator.calculate(vulnerability)); + vulnerabilityRepository.save(vulnerabilityEntity); + log.info("Vulnerability processed successfully for ID: {}", vulnerabilityEntity.getId()); + } catch (Exception e) { + log.error("Failed to process vulnerability with ID: {}", vulnerabilityEntity.getId(), e); + throw new RuntimeException("Scanner processing error", e); + } + } +} diff --git a/backend/libs/scanning/src/main/java/dev/cleat/scanning/service/CodeScanAlertService.java b/backend/libs/scanning/src/main/java/dev/cleat/scanning/service/CodeScanAlertService.java new file mode 100644 index 00000000..c0b5649b --- /dev/null +++ b/backend/libs/scanning/src/main/java/dev/cleat/scanning/service/CodeScanAlertService.java @@ -0,0 +1,28 @@ +package dev.cleat.scanning.service; + +import dev.cleat.common.dto.request.CodeScanAlertRequestDto; +import dev.cleat.common.dto.response.CodeScanAlertResponseDto; +import dev.cleat.persistence.entity.CodeScanAlertEntity; +import dev.cleat.persistence.mapper.CodeScanAlertMapper; +import dev.cleat.scanning.CodeScanAlertScanner; +import org.springframework.stereotype.Service; + +@Service +public class CodeScanAlertService { + + private final CodeScanAlertMapper codeScanAlertMapper; + private final CodeScanAlertScanner codeScanAlertScanner; + + public CodeScanAlertService(CodeScanAlertMapper codeScanAlertMapper, CodeScanAlertScanner codeScanAlertScanner) { + this.codeScanAlertMapper = codeScanAlertMapper; + this.codeScanAlertScanner = codeScanAlertScanner; + } + + public CodeScanAlertResponseDto create(CodeScanAlertRequestDto codeScanAlertRequestDto) { + CodeScanAlertEntity codeScanAlertEntity = codeScanAlertMapper.toCodeScanAlertEntity(codeScanAlertRequestDto); + + codeScanAlertScanner.process(codeScanAlertEntity); + + return codeScanAlertMapper.toCodeScanAlertDto(codeScanAlertEntity); + } +} diff --git a/backend/libs/scanning/src/main/java/dev/cleat/scanning/service/SecretFindingService.java b/backend/libs/scanning/src/main/java/dev/cleat/scanning/service/SecretFindingService.java new file mode 100644 index 00000000..c00148b0 --- /dev/null +++ b/backend/libs/scanning/src/main/java/dev/cleat/scanning/service/SecretFindingService.java @@ -0,0 +1,29 @@ +package dev.cleat.scanning.service; + +import dev.cleat.common.dto.request.SecretFindingRequestDto; +import dev.cleat.common.dto.response.SecretFindingResponseDto; +import dev.cleat.persistence.entity.SecretFindingEntity; +import dev.cleat.persistence.mapper.SecretFindingMapper; +import dev.cleat.scanning.SecretFindingScanner; +import org.springframework.stereotype.Service; + +@Service +public class SecretFindingService { + + private final SecretFindingMapper secretFindingMapper; + private final SecretFindingScanner secretScanner; + + public SecretFindingService(SecretFindingMapper secretFindingMapper, SecretFindingScanner secretScanner) { + this.secretFindingMapper = secretFindingMapper; + this.secretScanner = secretScanner; + } + + public SecretFindingResponseDto create(SecretFindingRequestDto secretFindingRequestDto) { + + SecretFindingEntity secretFindingEntity = secretFindingMapper.toSecretFindingEntity(secretFindingRequestDto); + + secretScanner.process(secretFindingEntity); + + return secretFindingMapper.toSecretFindingDto(secretFindingEntity); + } +} diff --git a/backend/libs/scanning/src/main/java/dev/cleat/scanning/service/VulnerabilityService.java b/backend/libs/scanning/src/main/java/dev/cleat/scanning/service/VulnerabilityService.java new file mode 100644 index 00000000..b0b0e95d --- /dev/null +++ b/backend/libs/scanning/src/main/java/dev/cleat/scanning/service/VulnerabilityService.java @@ -0,0 +1,48 @@ +package dev.cleat.scanning.service; + +import dev.cleat.common.dto.request.VulnerabilityRequestDto; +import dev.cleat.common.dto.response.VulnerabilityResponseDto; +import dev.cleat.common.exception.NotFoundException; +import dev.cleat.persistence.entity.VulnerabilityEntity; +import dev.cleat.persistence.mapper.VulnerabilityMapper; +import dev.cleat.persistence.repository.VulnerabilityRepository; +import dev.cleat.scanning.VulnerabilityScanner; +import java.time.OffsetDateTime; +import java.util.UUID; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class VulnerabilityService { + + private final VulnerabilityMapper vulnerabilityMapper; + private final VulnerabilityScanner vulnerabilityScanner; + private final VulnerabilityRepository vulnerabilityRepository; + + public VulnerabilityService( + VulnerabilityMapper vulnerabilityMapper, + VulnerabilityScanner vulnerabilityScanner, + VulnerabilityRepository vulnerabilityRepository) { + this.vulnerabilityMapper = vulnerabilityMapper; + this.vulnerabilityScanner = vulnerabilityScanner; + this.vulnerabilityRepository = vulnerabilityRepository; + } + + @Transactional + public VulnerabilityResponseDto create(VulnerabilityRequestDto vulnerabilityRequestDto) { + VulnerabilityEntity vulnerabilityEntity = vulnerabilityMapper.toVulnerabilityEntity(vulnerabilityRequestDto); + if (vulnerabilityEntity.getPublishedAt() == null) { + vulnerabilityEntity.setPublishedAt(OffsetDateTime.now()); + } + vulnerabilityScanner.processAndSave(vulnerabilityEntity); + return vulnerabilityMapper.toVulnerabilityDto(vulnerabilityEntity); + } + + @Transactional(readOnly = true) + public VulnerabilityResponseDto findById(UUID id) { + return vulnerabilityRepository + .findById(id) + .map(vulnerabilityMapper::toVulnerabilityDto) + .orElseThrow(() -> new NotFoundException("Vulnerability not found")); + } +} diff --git a/backend/libs/scanning/src/test/java/dev/cleat/scanning/CodeScanAlertScannerTest.java b/backend/libs/scanning/src/test/java/dev/cleat/scanning/CodeScanAlertScannerTest.java new file mode 100644 index 00000000..356446e6 --- /dev/null +++ b/backend/libs/scanning/src/test/java/dev/cleat/scanning/CodeScanAlertScannerTest.java @@ -0,0 +1,39 @@ +package dev.cleat.scanning; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; + +import dev.cleat.common.enums.Severity; +import dev.cleat.common.enums.Status; +import dev.cleat.persistence.entity.CodeScanAlertEntity; +import dev.cleat.persistence.repository.CodeScanAlertRepository; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class CodeScanAlertScannerTest { + + @InjectMocks + CodeScanAlertScanner codeScanAlertScanner; + + @Mock + CodeScanAlertRepository codeScanAlertRepository; + + @Test + void whenProcessAlert_thenStatusShouldBeOpen() { + + // given + CodeScanAlertEntity codeScanAlertEntity = new CodeScanAlertEntity().setSeverity(Severity.CRITICAL); + + // when + codeScanAlertScanner.process(codeScanAlertEntity); + + // then + Assertions.assertEquals(Status.OPEN, codeScanAlertEntity.getStatus()); + verify(codeScanAlertRepository).save(any()); + } +} diff --git a/backend/libs/scanning/src/test/java/dev/cleat/scanning/SecretFindingScannerTest.java b/backend/libs/scanning/src/test/java/dev/cleat/scanning/SecretFindingScannerTest.java new file mode 100644 index 00000000..6244c805 --- /dev/null +++ b/backend/libs/scanning/src/test/java/dev/cleat/scanning/SecretFindingScannerTest.java @@ -0,0 +1,59 @@ +package dev.cleat.scanning; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; + +import dev.cleat.common.enums.Severity; +import dev.cleat.common.enums.Validity; +import dev.cleat.persistence.entity.RepoEntity; +import dev.cleat.persistence.entity.SecretFindingEntity; +import dev.cleat.persistence.repository.SecretFindingRepository; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class SecretFindingScannerTest { + + @InjectMocks + private SecretFindingScanner secretFindingScanner; + + @Mock + private SecretFindingRepository secretFindingRepository; + + @Test + void givenSecretNotBlockedByPushProtection_whenProcessing_thenSeverityShouldBeCritical() { + + // given + SecretFindingEntity secretFindingEntity = new SecretFindingEntity().setPushProtectionBlocked(false); + RepoEntity repoEntity = new RepoEntity().setName("test-repo"); + secretFindingEntity.setRepo(repoEntity); + // when + secretFindingScanner.process(secretFindingEntity); + + // then + Assertions.assertEquals(Severity.CRITICAL, secretFindingEntity.getSeverity()); + Assertions.assertEquals(Validity.ACTIVE, secretFindingEntity.getValidity()); + verify(secretFindingRepository).save(any()); + } + + @Test + void givenSecretBlockedByPushProtection_whenProcessing_thenSeverityShouldBeLow() { + + // given + SecretFindingEntity secretFindingEntity = new SecretFindingEntity().setPushProtectionBlocked(true); + RepoEntity repoEntity = new RepoEntity().setName("test-repo"); + secretFindingEntity.setRepo(repoEntity); + + // when + secretFindingScanner.process(secretFindingEntity); + + // then + Assertions.assertEquals(Severity.LOW, secretFindingEntity.getSeverity()); + Assertions.assertEquals(Validity.REVOKED, secretFindingEntity.getValidity()); + verify(secretFindingRepository).save(any()); + } +} diff --git a/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java b/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java new file mode 100644 index 00000000..74f5c899 --- /dev/null +++ b/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java @@ -0,0 +1,69 @@ +package dev.cleat.scanning; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import dev.cleat.common.enums.Priority; +import dev.cleat.domain.PriorityCalculator; +import dev.cleat.persistence.entity.VulnerabilityEntity; +import dev.cleat.persistence.repository.VulnerabilityRepository; +import java.util.Optional; +import java.util.UUID; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class VulnerabilityScannerTest { + @InjectMocks + private VulnerabilityScanner vulnerabilityScanner; + + @Mock + private VulnerabilityRepository vulnerabilityRepository; + + @Mock + private PriorityCalculator priorityCalculator; + + @Test + void givenVulnerabilityWithKevAndHighCvss_whenProcessing_thenPriorityShouldBeUrgent() { + // given + UUID uuid = UUID.randomUUID(); + VulnerabilityEntity vulnerabilityEntity = + new VulnerabilityEntity().setId(uuid).setKev(true).setCvss(9.5); + + when(vulnerabilityRepository.findById(uuid)).thenReturn(Optional.of(vulnerabilityEntity)); + + when(priorityCalculator.calculate(any())).thenReturn(Priority.URGENT); + + // when + vulnerabilityScanner.processAndSave(vulnerabilityEntity); + + // then + VulnerabilityEntity savedVulnerabilityEntity = + vulnerabilityRepository.findById(uuid).get(); + assertNotNull(savedVulnerabilityEntity.getPriority(), "Priority should not be null"); + assertEquals( + Priority.URGENT, + savedVulnerabilityEntity.getPriority(), + "Priority should be URGENT for high CVSS and KEV"); + } + + @Test + void whenCvssIsInvalid_thenShouldThrowException() { + + // given + VulnerabilityEntity vulnerabilityEntity = new VulnerabilityEntity().setCvss(15.0); + + when(priorityCalculator.calculate(any())).thenThrow(new IllegalArgumentException("Invalid CVSS")); + + // then + assertThrows(RuntimeException.class, () -> { + vulnerabilityScanner.processAndSave(vulnerabilityEntity); + }); + } +} From 0b84f18f3c72a340e88b7006d2642146b388c4b8 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Sun, 28 Jun 2026 23:45:56 +0400 Subject: [PATCH 02/50] fix(test): fix method naming convention and checkstyle violations --- .../test/java/dev/cleat/api/CodeScanAlertControllerTest.java | 2 +- .../test/java/dev/cleat/api/SecretFindingControllerTest.java | 2 +- .../test/java/dev/cleat/api/VulnerabilityControllerTest.java | 2 +- .../java/dev/cleat/scanning/CodeScanAlertScannerTest.java | 2 +- .../java/dev/cleat/scanning/SecretFindingScannerTest.java | 4 ++-- .../java/dev/cleat/scanning/VulnerabilityScannerTest.java | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/apps/api/src/test/java/dev/cleat/api/CodeScanAlertControllerTest.java b/backend/apps/api/src/test/java/dev/cleat/api/CodeScanAlertControllerTest.java index 127d195c..74df13d3 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/CodeScanAlertControllerTest.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/CodeScanAlertControllerTest.java @@ -31,7 +31,7 @@ public class CodeScanAlertControllerTest { private CodeScanAlertService codeScanAlertService; @Test - void whenCreateCodeScanAlert_thenShouldBeReturn201() throws Exception { + void whenCreateCodeScanAlertThenShouldBeReturn201() throws Exception { // given CodeScanAlertRequestDto codeScanAlertRequestDto = new CodeScanAlertRequestDto(); diff --git a/backend/apps/api/src/test/java/dev/cleat/api/SecretFindingControllerTest.java b/backend/apps/api/src/test/java/dev/cleat/api/SecretFindingControllerTest.java index eef29d44..45461372 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/SecretFindingControllerTest.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/SecretFindingControllerTest.java @@ -31,7 +31,7 @@ public class SecretFindingControllerTest { private SecretFindingService secretFindingService; @Test - void whenCreateSecret_thenShouldReturn201() throws Exception { + void whenCreateSecretThenShouldReturn201() throws Exception { // given SecretFindingRequestDto secretFindingRequestDto = new SecretFindingRequestDto(); diff --git a/backend/apps/api/src/test/java/dev/cleat/api/VulnerabilityControllerTest.java b/backend/apps/api/src/test/java/dev/cleat/api/VulnerabilityControllerTest.java index 9f0555de..fe29f355 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/VulnerabilityControllerTest.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/VulnerabilityControllerTest.java @@ -31,7 +31,7 @@ public class VulnerabilityControllerTest { private VulnerabilityService vulnerabilityService; @Test - void whenCreateVulnerability_thenShouldReturn201() throws Exception { + void whenCreateVulnerabilityThenShouldReturn201() throws Exception { // given VulnerabilityRequestDto vulnerabilityRequestDto = new VulnerabilityRequestDto(); diff --git a/backend/libs/scanning/src/test/java/dev/cleat/scanning/CodeScanAlertScannerTest.java b/backend/libs/scanning/src/test/java/dev/cleat/scanning/CodeScanAlertScannerTest.java index 356446e6..2c72bdd8 100644 --- a/backend/libs/scanning/src/test/java/dev/cleat/scanning/CodeScanAlertScannerTest.java +++ b/backend/libs/scanning/src/test/java/dev/cleat/scanning/CodeScanAlertScannerTest.java @@ -24,7 +24,7 @@ public class CodeScanAlertScannerTest { CodeScanAlertRepository codeScanAlertRepository; @Test - void whenProcessAlert_thenStatusShouldBeOpen() { + void whenProcessAlertThenStatusShouldBeOpen() { // given CodeScanAlertEntity codeScanAlertEntity = new CodeScanAlertEntity().setSeverity(Severity.CRITICAL); diff --git a/backend/libs/scanning/src/test/java/dev/cleat/scanning/SecretFindingScannerTest.java b/backend/libs/scanning/src/test/java/dev/cleat/scanning/SecretFindingScannerTest.java index 6244c805..8aa51253 100644 --- a/backend/libs/scanning/src/test/java/dev/cleat/scanning/SecretFindingScannerTest.java +++ b/backend/libs/scanning/src/test/java/dev/cleat/scanning/SecretFindingScannerTest.java @@ -25,7 +25,7 @@ public class SecretFindingScannerTest { private SecretFindingRepository secretFindingRepository; @Test - void givenSecretNotBlockedByPushProtection_whenProcessing_thenSeverityShouldBeCritical() { + void givenSecretNotBlockedByPushProtectionWhenProcessingThenSeverityShouldBeCritical() { // given SecretFindingEntity secretFindingEntity = new SecretFindingEntity().setPushProtectionBlocked(false); @@ -41,7 +41,7 @@ void givenSecretNotBlockedByPushProtection_whenProcessing_thenSeverityShouldBeCr } @Test - void givenSecretBlockedByPushProtection_whenProcessing_thenSeverityShouldBeLow() { + void givenSecretBlockedByPushProtectionWhenProcessingThenSeverityShouldBeLow() { // given SecretFindingEntity secretFindingEntity = new SecretFindingEntity().setPushProtectionBlocked(true); diff --git a/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java b/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java index 74f5c899..ddb8ac0d 100644 --- a/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java +++ b/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java @@ -30,7 +30,7 @@ public class VulnerabilityScannerTest { private PriorityCalculator priorityCalculator; @Test - void givenVulnerabilityWithKevAndHighCvss_whenProcessing_thenPriorityShouldBeUrgent() { + void givenVulnerabilityWithKevAndHighCvssWhenProcessingThenPriorityShouldBeUrgent() { // given UUID uuid = UUID.randomUUID(); VulnerabilityEntity vulnerabilityEntity = @@ -54,7 +54,7 @@ void givenVulnerabilityWithKevAndHighCvss_whenProcessing_thenPriorityShouldBeUrg } @Test - void whenCvssIsInvalid_thenShouldThrowException() { + void whenCvssIsInvalidThenShouldThrowException() { // given VulnerabilityEntity vulnerabilityEntity = new VulnerabilityEntity().setCvss(15.0); From 2b59ea881b2ef6ad9f73c9fe04a4ffbdc2647fae Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 00:08:14 +0400 Subject: [PATCH 03/50] fix(test): resolve checkstyle violations and dependency injection errors --- .../src/test/java/dev/cleat/api/CleatApiTests.java | 2 +- .../java/dev/cleat/worker/VulnerabilityWorker.java | 11 ++++++----- .../java/dev/cleat/scanning/CodeScanAlertScanner.java | 6 +++--- .../java/dev/cleat/scanning/SecretFindingScanner.java | 6 +++--- .../java/dev/cleat/scanning/VulnerabilityScanner.java | 6 +++--- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java index eb41e9f1..e66f442c 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java @@ -3,7 +3,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -@SpringBootTest +@SpringBootTest(classes = CleatApiApplication.class) public class CleatApiTests extends AbstractIntegrationTest { @Test diff --git a/backend/apps/worker/src/main/java/dev/cleat/worker/VulnerabilityWorker.java b/backend/apps/worker/src/main/java/dev/cleat/worker/VulnerabilityWorker.java index 387433e8..55be546a 100644 --- a/backend/apps/worker/src/main/java/dev/cleat/worker/VulnerabilityWorker.java +++ b/backend/apps/worker/src/main/java/dev/cleat/worker/VulnerabilityWorker.java @@ -1,6 +1,6 @@ package dev.cleat.worker; -import dev.cleat.persistence.entity.VulnerabilityEntity; +import dev.cleat.persistence.repository.VulnerabilityRepository; import dev.cleat.scanning.VulnerabilityScanner; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -8,15 +8,16 @@ @Component public class VulnerabilityWorker { private final VulnerabilityScanner vulnerabilityScanner; - private final VulnerabilityEntity vulnerabilityEntity; + private final VulnerabilityRepository vulnerabilityRepository; - public VulnerabilityWorker(VulnerabilityScanner vulnerabilityScanner, VulnerabilityEntity vulnerabilityEntity) { + public VulnerabilityWorker( + VulnerabilityScanner vulnerabilityScanner, VulnerabilityRepository vulnerabilityRepository) { this.vulnerabilityScanner = vulnerabilityScanner; - this.vulnerabilityEntity = vulnerabilityEntity; + this.vulnerabilityRepository = vulnerabilityRepository; } @Scheduled(fixedDelay = 600000) public void runSecurityScan() { - vulnerabilityScanner.processAndSave(vulnerabilityEntity); + vulnerabilityRepository.findAll().forEach(vulnerabilityScanner::processAndSave); } } diff --git a/backend/libs/scanning/src/main/java/dev/cleat/scanning/CodeScanAlertScanner.java b/backend/libs/scanning/src/main/java/dev/cleat/scanning/CodeScanAlertScanner.java index 4d827af8..b7f2a34d 100644 --- a/backend/libs/scanning/src/main/java/dev/cleat/scanning/CodeScanAlertScanner.java +++ b/backend/libs/scanning/src/main/java/dev/cleat/scanning/CodeScanAlertScanner.java @@ -10,7 +10,7 @@ @Service public class CodeScanAlertScanner { - private static final Logger log = LoggerFactory.getLogger(CodeScanAlertScanner.class); + private static final Logger LOG = LoggerFactory.getLogger(CodeScanAlertScanner.class); private final CodeScanAlertRepository codeScanAlertRepository; public CodeScanAlertScanner(CodeScanAlertRepository codeScanAlertRepository) { @@ -18,7 +18,7 @@ public CodeScanAlertScanner(CodeScanAlertRepository codeScanAlertRepository) { } public void process(CodeScanAlertEntity codeScanAlertEntity) { - log.info("Analyzing code scan alert for rule: {}", codeScanAlertEntity.getRule()); + LOG.info("Analyzing code scan alert for rule: {}", codeScanAlertEntity.getRule()); if (Severity.CRITICAL.equals(codeScanAlertEntity.getSeverity()) || Severity.HIGH.equals(codeScanAlertEntity.getSeverity())) { codeScanAlertEntity.setStatus(Status.OPEN); @@ -26,6 +26,6 @@ public void process(CodeScanAlertEntity codeScanAlertEntity) { codeScanAlertEntity.setStatus(Status.OPEN); } codeScanAlertRepository.save(codeScanAlertEntity); - log.info("Code scan alert processed successfully with status: {}", codeScanAlertEntity.getStatus()); + LOG.info("Code scan alert processed successfully with status: {}", codeScanAlertEntity.getStatus()); } } diff --git a/backend/libs/scanning/src/main/java/dev/cleat/scanning/SecretFindingScanner.java b/backend/libs/scanning/src/main/java/dev/cleat/scanning/SecretFindingScanner.java index 9a8355fd..0ae459eb 100644 --- a/backend/libs/scanning/src/main/java/dev/cleat/scanning/SecretFindingScanner.java +++ b/backend/libs/scanning/src/main/java/dev/cleat/scanning/SecretFindingScanner.java @@ -10,7 +10,7 @@ @Service public class SecretFindingScanner { - private static final Logger log = LoggerFactory.getLogger(SecretFindingScanner.class); + private static final Logger LOG = LoggerFactory.getLogger(SecretFindingScanner.class); private final SecretFindingRepository secretFindingRepository; public SecretFindingScanner(SecretFindingRepository secretFindingRepository) { @@ -18,7 +18,7 @@ public SecretFindingScanner(SecretFindingRepository secretFindingRepository) { } public void process(SecretFindingEntity secretFindingEntity) { - log.info( + LOG.info( "Processing secret finding for repo: {}", secretFindingEntity.getRepo().getName()); if (Boolean.FALSE.equals(secretFindingEntity.getPushProtectionBlocked())) { @@ -27,6 +27,6 @@ public void process(SecretFindingEntity secretFindingEntity) { secretFindingEntity.setSeverity(Severity.LOW).setValidity(Validity.REVOKED); } secretFindingRepository.save(secretFindingEntity); - log.info("Secret finding processed successfully: {}", secretFindingEntity.getId()); + LOG.info("Secret finding processed successfully: {}", secretFindingEntity.getId()); } } diff --git a/backend/libs/scanning/src/main/java/dev/cleat/scanning/VulnerabilityScanner.java b/backend/libs/scanning/src/main/java/dev/cleat/scanning/VulnerabilityScanner.java index 33986562..66fe5cf5 100644 --- a/backend/libs/scanning/src/main/java/dev/cleat/scanning/VulnerabilityScanner.java +++ b/backend/libs/scanning/src/main/java/dev/cleat/scanning/VulnerabilityScanner.java @@ -12,7 +12,7 @@ public class VulnerabilityScanner { private final PriorityCalculator priorityCalculator; private final VulnerabilityRepository vulnerabilityRepository; - private static final Logger log = LoggerFactory.getLogger(VulnerabilityScanner.class); + private static final Logger LOG = LoggerFactory.getLogger(VulnerabilityScanner.class); public VulnerabilityScanner( PriorityCalculator priorityCalculator, VulnerabilityRepository vulnerabilityRepository) { @@ -31,9 +31,9 @@ public void processAndSave(VulnerabilityEntity vulnerabilityEntity) { vulnerabilityEntity.getKev(), vulnerabilityEntity.getCvss(), vulnerabilityEntity.getSeverity()); vulnerabilityEntity.setPriority(priorityCalculator.calculate(vulnerability)); vulnerabilityRepository.save(vulnerabilityEntity); - log.info("Vulnerability processed successfully for ID: {}", vulnerabilityEntity.getId()); + LOG.info("Vulnerability processed successfully for ID: {}", vulnerabilityEntity.getId()); } catch (Exception e) { - log.error("Failed to process vulnerability with ID: {}", vulnerabilityEntity.getId(), e); + LOG.error("Failed to process vulnerability with ID: {}", vulnerabilityEntity.getId(), e); throw new RuntimeException("Scanner processing error", e); } } From 0ef3c0ec7d91e00b96bfdcb735f69c74f06f8701 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 00:23:28 +0400 Subject: [PATCH 04/50] fix(worker): enable component scanning --- .../src/main/java/dev/cleat/worker/CleatWorkerApplication.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/apps/worker/src/main/java/dev/cleat/worker/CleatWorkerApplication.java b/backend/apps/worker/src/main/java/dev/cleat/worker/CleatWorkerApplication.java index abb0d983..a5554749 100644 --- a/backend/apps/worker/src/main/java/dev/cleat/worker/CleatWorkerApplication.java +++ b/backend/apps/worker/src/main/java/dev/cleat/worker/CleatWorkerApplication.java @@ -4,7 +4,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; -@SpringBootApplication(scanBasePackages = {"dev.cleat.persistence", "dev.cleat.scanning"}) +@SpringBootApplication(scanBasePackages = {"dev.cleat.persistence", "dev.cleat.scanning", "dev.cleat.worker"}) @EnableScheduling public class CleatWorkerApplication { From 796d6b543b7fab5ce7e8a559ce77dc016f87dc31 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 00:31:07 +0400 Subject: [PATCH 05/50] fix(test): align worker and api tests with application context --- .../java/dev/cleat/worker/CleatWorkerApplicationTests.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index 7a6f1f44..9c63cb8a 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -1,11 +1,16 @@ package dev.cleat.worker; +import dev.cleat.scanning.VulnerabilityScanner; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.bean.override.mockito.MockitoBean; -@SpringBootTest +@SpringBootTest(classes = CleatWorkerApplication.class) public class CleatWorkerApplicationTests extends AbstractIntegrationTest { + @MockitoBean + private VulnerabilityScanner vulnerabilityScanner; + @Test void contextLoads() {} } From e63e6e9b92291aa24c1897388bc5e3d99f386b2b Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 00:58:43 +0400 Subject: [PATCH 06/50] refactor(test): decouple SpringBootTest from AbstractIntegrationTest to fix context loading --- .../src/test/java/dev/cleat/worker/AbstractIntegrationTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/AbstractIntegrationTest.java b/backend/apps/worker/src/test/java/dev/cleat/worker/AbstractIntegrationTest.java index 63842d66..16315f1a 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/AbstractIntegrationTest.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/AbstractIntegrationTest.java @@ -1,6 +1,5 @@ package dev.cleat.worker; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.PostgreSQLContainer; @@ -8,7 +7,6 @@ import org.testcontainers.junit.jupiter.Testcontainers; @Testcontainers -@SpringBootTest public abstract class AbstractIntegrationTest { @Container From d16e765ee241f9fef4e4df18ef95ff84160e8bb0 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 01:18:25 +0400 Subject: [PATCH 07/50] fix(test): exclude data source auto-config in integration tests --- .../java/dev/cleat/worker/CleatWorkerApplicationTests.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index 9c63cb8a..6582f729 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -5,7 +5,12 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.bean.override.mockito.MockitoBean; -@SpringBootTest(classes = CleatWorkerApplication.class) +@SpringBootTest( + classes = CleatWorkerApplication.class, + properties = { + "spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration" + + ",org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration" + }) public class CleatWorkerApplicationTests extends AbstractIntegrationTest { @MockitoBean From b0167ac6cc71af94dc3f38d6ee8177ee9759fb82 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 01:23:32 +0400 Subject: [PATCH 08/50] fix(test): mock service dependencies in worker test --- .../dev/cleat/worker/CleatWorkerApplicationTests.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index 6582f729..2058385d 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -1,5 +1,7 @@ package dev.cleat.worker; +import dev.cleat.domain.PriorityCalculator; +import dev.cleat.persistence.repository.VulnerabilityRepository; import dev.cleat.scanning.VulnerabilityScanner; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @@ -16,6 +18,12 @@ public class CleatWorkerApplicationTests extends AbstractIntegrationTest { @MockitoBean private VulnerabilityScanner vulnerabilityScanner; + @MockitoBean + private PriorityCalculator priorityCalculator; + + @MockitoBean + private VulnerabilityRepository vulnerabilityRepository; + @Test void contextLoads() {} } From 9705041ecb64544a5bfc54b43c61a2a858c3cbf1 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 12:18:27 +0400 Subject: [PATCH 09/50] test: decouple SpringBootTest from the integration base and remove redundant exclude configurations --- .../test/java/dev/cleat/api/AbstractIntegrationTest.java | 2 -- .../java/dev/cleat/worker/CleatWorkerApplicationTests.java | 7 +------ 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/backend/apps/api/src/test/java/dev/cleat/api/AbstractIntegrationTest.java b/backend/apps/api/src/test/java/dev/cleat/api/AbstractIntegrationTest.java index 7af426b4..de0c0302 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/AbstractIntegrationTest.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/AbstractIntegrationTest.java @@ -1,6 +1,5 @@ package dev.cleat.api; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.PostgreSQLContainer; @@ -8,7 +7,6 @@ import org.testcontainers.junit.jupiter.Testcontainers; @Testcontainers -@SpringBootTest public abstract class AbstractIntegrationTest { @Container diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index 2058385d..e5bf5689 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -7,12 +7,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.bean.override.mockito.MockitoBean; -@SpringBootTest( - classes = CleatWorkerApplication.class, - properties = { - "spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration" - + ",org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration" - }) +@SpringBootTest public class CleatWorkerApplicationTests extends AbstractIntegrationTest { @MockitoBean From b3728a505589d845cc8300cbe298edd30d28bc98 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 12:52:20 +0400 Subject: [PATCH 10/50] test: refactor integration tests with active profiles and testcontainers --- backend/apps/api/src/main/resources/application-test.yml | 7 +++++++ .../api/src/test/java/dev/cleat/api/CleatApiTests.java | 2 ++ .../apps/worker/src/main/resources/application-test.yml | 7 +++++++ .../java/dev/cleat/worker/CleatWorkerApplicationTests.java | 2 ++ 4 files changed, 18 insertions(+) create mode 100644 backend/apps/api/src/main/resources/application-test.yml create mode 100644 backend/apps/worker/src/main/resources/application-test.yml diff --git a/backend/apps/api/src/main/resources/application-test.yml b/backend/apps/api/src/main/resources/application-test.yml new file mode 100644 index 00000000..ebf9e3f2 --- /dev/null +++ b/backend/apps/api/src/main/resources/application-test.yml @@ -0,0 +1,7 @@ +spring: + datasource: + url: jdbc:tc:postgresql:16-alpine:///cleat_db + driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver + jpa: + hibernate: + ddl-auto: update \ No newline at end of file diff --git a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java index e66f442c..d7592af0 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java @@ -2,8 +2,10 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; @SpringBootTest(classes = CleatApiApplication.class) +@ActiveProfiles("test") public class CleatApiTests extends AbstractIntegrationTest { @Test diff --git a/backend/apps/worker/src/main/resources/application-test.yml b/backend/apps/worker/src/main/resources/application-test.yml new file mode 100644 index 00000000..7ba0a05c --- /dev/null +++ b/backend/apps/worker/src/main/resources/application-test.yml @@ -0,0 +1,7 @@ +spring: + datasource: + url: jdbc:tc:postgresql:16-alpine:///cleat_db + driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver + jpa: + hibernate: + ddl-auto: update diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index e5bf5689..66138877 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -5,9 +5,11 @@ import dev.cleat.scanning.VulnerabilityScanner; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.bean.override.mockito.MockitoBean; @SpringBootTest +@ActiveProfiles("test") public class CleatWorkerApplicationTests extends AbstractIntegrationTest { @MockitoBean From c37478c5871bef49962ab598585c3537ff2939b5 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 13:36:41 +0400 Subject: [PATCH 11/50] test: isolate test context with application-test.yml and disable auto-configuration for CI --- .../apps/api/src/main/resources/application-test.yml | 12 +++++++++++- .../src/test/java/dev/cleat/api/CleatApiTests.java | 2 ++ .../worker/src/main/resources/application-test.yml | 10 ++++++++++ .../cleat/worker/CleatWorkerApplicationTests.java | 2 ++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/backend/apps/api/src/main/resources/application-test.yml b/backend/apps/api/src/main/resources/application-test.yml index ebf9e3f2..4eb451f3 100644 --- a/backend/apps/api/src/main/resources/application-test.yml +++ b/backend/apps/api/src/main/resources/application-test.yml @@ -1,7 +1,17 @@ spring: + autoconfigure: + exclude: + - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration + - org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration + - org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration datasource: url: jdbc:tc:postgresql:16-alpine:///cleat_db driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver jpa: hibernate: - ddl-auto: update \ No newline at end of file + ddl-auto: update + + data: + redis: + host: ${redis.host:localhost} + port: ${redis.port:6379} \ No newline at end of file diff --git a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java index d7592af0..cca88820 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java @@ -1,11 +1,13 @@ package dev.cleat.api; import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; @SpringBootTest(classes = CleatApiApplication.class) @ActiveProfiles("test") +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) public class CleatApiTests extends AbstractIntegrationTest { @Test diff --git a/backend/apps/worker/src/main/resources/application-test.yml b/backend/apps/worker/src/main/resources/application-test.yml index 7ba0a05c..4eb451f3 100644 --- a/backend/apps/worker/src/main/resources/application-test.yml +++ b/backend/apps/worker/src/main/resources/application-test.yml @@ -1,7 +1,17 @@ spring: + autoconfigure: + exclude: + - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration + - org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration + - org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration datasource: url: jdbc:tc:postgresql:16-alpine:///cleat_db driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver jpa: hibernate: ddl-auto: update + + data: + redis: + host: ${redis.host:localhost} + port: ${redis.port:6379} \ No newline at end of file diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index 66138877..66ae2d9f 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -4,12 +4,14 @@ import dev.cleat.persistence.repository.VulnerabilityRepository; import dev.cleat.scanning.VulnerabilityScanner; import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.bean.override.mockito.MockitoBean; @SpringBootTest @ActiveProfiles("test") +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) public class CleatWorkerApplicationTests extends AbstractIntegrationTest { @MockitoBean From 398edbedeba1af04572002c04fae6b729afc3aac Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 13:44:30 +0400 Subject: [PATCH 12/50] test: add classes parameter to @SpringBootTest in worker module --- .../test/java/dev/cleat/worker/CleatWorkerApplicationTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index 66ae2d9f..9b9cda27 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -9,7 +9,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.bean.override.mockito.MockitoBean; -@SpringBootTest +@SpringBootTest(classes = CleatWorkerApplication.class) @ActiveProfiles("test") @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) public class CleatWorkerApplicationTests extends AbstractIntegrationTest { From c475c446eb72c2174f8830fc11a7425c3cc1bf42 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 13:49:39 +0400 Subject: [PATCH 13/50] test: add @ComponentScan annotation to CleatWorkerApplicationTests --- .../test/java/dev/cleat/worker/CleatWorkerApplicationTests.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index 9b9cda27..2a27881a 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -6,12 +6,14 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.ComponentScan; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.bean.override.mockito.MockitoBean; @SpringBootTest(classes = CleatWorkerApplication.class) @ActiveProfiles("test") @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@ComponentScan(basePackages = "dev.cleat") public class CleatWorkerApplicationTests extends AbstractIntegrationTest { @MockitoBean From 5280e5f1d83400cca9b7b3d3460546383e16b23d Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 13:53:21 +0400 Subject: [PATCH 14/50] test: add @ComponentScan annotation to CleatApiApplicationTests --- backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java index cca88820..9c509da0 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java @@ -3,11 +3,13 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.ComponentScan; import org.springframework.test.context.ActiveProfiles; @SpringBootTest(classes = CleatApiApplication.class) @ActiveProfiles("test") @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@ComponentScan(basePackages = "dev.cleat") public class CleatApiTests extends AbstractIntegrationTest { @Test From d68b7bdf10d3a9c04a89ad7e533f66efbfd2a30b Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 14:02:50 +0400 Subject: [PATCH 15/50] test: clean integration test context and remove redundant mock beans --- .../test/java/dev/cleat/api/CleatApiTests.java | 2 -- .../cleat/worker/CleatWorkerApplicationTests.java | 15 --------------- 2 files changed, 17 deletions(-) diff --git a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java index 9c509da0..cca88820 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java @@ -3,13 +3,11 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.ComponentScan; import org.springframework.test.context.ActiveProfiles; @SpringBootTest(classes = CleatApiApplication.class) @ActiveProfiles("test") @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@ComponentScan(basePackages = "dev.cleat") public class CleatApiTests extends AbstractIntegrationTest { @Test diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index 2a27881a..ec00f7f1 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -1,30 +1,15 @@ package dev.cleat.worker; -import dev.cleat.domain.PriorityCalculator; -import dev.cleat.persistence.repository.VulnerabilityRepository; -import dev.cleat.scanning.VulnerabilityScanner; import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.ComponentScan; import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.bean.override.mockito.MockitoBean; @SpringBootTest(classes = CleatWorkerApplication.class) @ActiveProfiles("test") @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@ComponentScan(basePackages = "dev.cleat") public class CleatWorkerApplicationTests extends AbstractIntegrationTest { - @MockitoBean - private VulnerabilityScanner vulnerabilityScanner; - - @MockitoBean - private PriorityCalculator priorityCalculator; - - @MockitoBean - private VulnerabilityRepository vulnerabilityRepository; - @Test void contextLoads() {} } From 908757a0cc17d4b3c3d999860a09ef4f96aac9a2 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 14:13:48 +0400 Subject: [PATCH 16/50] test: remove auto-configuration exclusion to allow Testcontainers to manage datasource --- backend/apps/api/src/main/resources/application-test.yml | 5 ----- backend/apps/worker/src/main/resources/application-test.yml | 5 ----- 2 files changed, 10 deletions(-) diff --git a/backend/apps/api/src/main/resources/application-test.yml b/backend/apps/api/src/main/resources/application-test.yml index 4eb451f3..f70554e1 100644 --- a/backend/apps/api/src/main/resources/application-test.yml +++ b/backend/apps/api/src/main/resources/application-test.yml @@ -1,9 +1,4 @@ spring: - autoconfigure: - exclude: - - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration - - org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration - - org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration datasource: url: jdbc:tc:postgresql:16-alpine:///cleat_db driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver diff --git a/backend/apps/worker/src/main/resources/application-test.yml b/backend/apps/worker/src/main/resources/application-test.yml index 4eb451f3..f70554e1 100644 --- a/backend/apps/worker/src/main/resources/application-test.yml +++ b/backend/apps/worker/src/main/resources/application-test.yml @@ -1,9 +1,4 @@ spring: - autoconfigure: - exclude: - - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration - - org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration - - org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration datasource: url: jdbc:tc:postgresql:16-alpine:///cleat_db driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver From 288381f1aa20a4e2a450d1ea3e59b6b1b4386fc7 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 14:34:19 +0400 Subject: [PATCH 17/50] test: resolve NoUniqueBeanDefinitionException by providing primary datasource in test configuration --- .../api/src/main/resources/application-test.yml | 2 ++ .../test/java/dev/cleat/api/CleatApiTests.java | 17 +++++++++++++++++ .../src/main/resources/application-test.yml | 2 ++ .../worker/CleatWorkerApplicationTests.java | 17 +++++++++++++++++ 4 files changed, 38 insertions(+) diff --git a/backend/apps/api/src/main/resources/application-test.yml b/backend/apps/api/src/main/resources/application-test.yml index f70554e1..298200a1 100644 --- a/backend/apps/api/src/main/resources/application-test.yml +++ b/backend/apps/api/src/main/resources/application-test.yml @@ -1,4 +1,6 @@ spring: + main: + allow-bean-definition-overriding: true datasource: url: jdbc:tc:postgresql:16-alpine:///cleat_db driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver diff --git a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java index cca88820..a378557c 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java @@ -1,8 +1,13 @@ package dev.cleat.api; +import javax.sql.DataSource; import org.junit.jupiter.api.Test; +import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; import org.springframework.test.context.ActiveProfiles; @SpringBootTest(classes = CleatApiApplication.class) @@ -12,4 +17,16 @@ public class CleatApiTests extends AbstractIntegrationTest { @Test void contextLoad() {} + + @TestConfiguration + static class TestConfig { + @Primary + @Bean + public DataSource dataSource() { + return DataSourceBuilder.create() + .url("jdbc:tc:postgresql:16-alpine:///cleat_db") + .driverClassName("org.testcontainers.jdbc.ContainerDatabaseDriver") + .build(); + } + } } diff --git a/backend/apps/worker/src/main/resources/application-test.yml b/backend/apps/worker/src/main/resources/application-test.yml index f70554e1..298200a1 100644 --- a/backend/apps/worker/src/main/resources/application-test.yml +++ b/backend/apps/worker/src/main/resources/application-test.yml @@ -1,4 +1,6 @@ spring: + main: + allow-bean-definition-overriding: true datasource: url: jdbc:tc:postgresql:16-alpine:///cleat_db driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index ec00f7f1..467a00c5 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -1,8 +1,13 @@ package dev.cleat.worker; +import javax.sql.DataSource; import org.junit.jupiter.api.Test; +import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; import org.springframework.test.context.ActiveProfiles; @SpringBootTest(classes = CleatWorkerApplication.class) @@ -12,4 +17,16 @@ public class CleatWorkerApplicationTests extends AbstractIntegrationTest { @Test void contextLoads() {} + + @TestConfiguration + static class TestConfig { + @Primary + @Bean + public DataSource dataSource() { + return DataSourceBuilder.create() + .url("jdbc:tc:postgresql:16-alpine:///cleat_db") + .driverClassName("org.testcontainers.jdbc.ContainerDatabaseDriver") + .build(); + } + } } From e44793761c0454c96998ea60d9e5aab88954879e Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 14:45:46 +0400 Subject: [PATCH 18/50] test: remove redundant application-test.properties configurations to rely on @ServiceConnection --- .../api/src/main/resources/application-test.yml | 14 -------------- .../test/java/dev/cleat/api/CleatApiTests.java | 17 ----------------- .../src/main/resources/application-test.yml | 14 -------------- .../worker/CleatWorkerApplicationTests.java | 17 ----------------- 4 files changed, 62 deletions(-) delete mode 100644 backend/apps/api/src/main/resources/application-test.yml delete mode 100644 backend/apps/worker/src/main/resources/application-test.yml diff --git a/backend/apps/api/src/main/resources/application-test.yml b/backend/apps/api/src/main/resources/application-test.yml deleted file mode 100644 index 298200a1..00000000 --- a/backend/apps/api/src/main/resources/application-test.yml +++ /dev/null @@ -1,14 +0,0 @@ -spring: - main: - allow-bean-definition-overriding: true - datasource: - url: jdbc:tc:postgresql:16-alpine:///cleat_db - driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver - jpa: - hibernate: - ddl-auto: update - - data: - redis: - host: ${redis.host:localhost} - port: ${redis.port:6379} \ No newline at end of file diff --git a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java index a378557c..cca88820 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java @@ -1,13 +1,8 @@ package dev.cleat.api; -import javax.sql.DataSource; import org.junit.jupiter.api.Test; -import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Primary; import org.springframework.test.context.ActiveProfiles; @SpringBootTest(classes = CleatApiApplication.class) @@ -17,16 +12,4 @@ public class CleatApiTests extends AbstractIntegrationTest { @Test void contextLoad() {} - - @TestConfiguration - static class TestConfig { - @Primary - @Bean - public DataSource dataSource() { - return DataSourceBuilder.create() - .url("jdbc:tc:postgresql:16-alpine:///cleat_db") - .driverClassName("org.testcontainers.jdbc.ContainerDatabaseDriver") - .build(); - } - } } diff --git a/backend/apps/worker/src/main/resources/application-test.yml b/backend/apps/worker/src/main/resources/application-test.yml deleted file mode 100644 index 298200a1..00000000 --- a/backend/apps/worker/src/main/resources/application-test.yml +++ /dev/null @@ -1,14 +0,0 @@ -spring: - main: - allow-bean-definition-overriding: true - datasource: - url: jdbc:tc:postgresql:16-alpine:///cleat_db - driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver - jpa: - hibernate: - ddl-auto: update - - data: - redis: - host: ${redis.host:localhost} - port: ${redis.port:6379} \ No newline at end of file diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index 467a00c5..ec00f7f1 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -1,13 +1,8 @@ package dev.cleat.worker; -import javax.sql.DataSource; import org.junit.jupiter.api.Test; -import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Primary; import org.springframework.test.context.ActiveProfiles; @SpringBootTest(classes = CleatWorkerApplication.class) @@ -17,16 +12,4 @@ public class CleatWorkerApplicationTests extends AbstractIntegrationTest { @Test void contextLoads() {} - - @TestConfiguration - static class TestConfig { - @Primary - @Bean - public DataSource dataSource() { - return DataSourceBuilder.create() - .url("jdbc:tc:postgresql:16-alpine:///cleat_db") - .driverClassName("org.testcontainers.jdbc.ContainerDatabaseDriver") - .build(); - } - } } From 6d6949bebfeef6097358b96f35d2dc8b29688567 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 14:56:34 +0400 Subject: [PATCH 19/50] test: add @EnableJpaRepository and @EntityScan to fix multi-module bean discovery --- .../apps/api/src/test/java/dev/cleat/api/CleatApiTests.java | 6 ++++-- .../java/dev/cleat/worker/CleatWorkerApplicationTests.java | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java index cca88820..7c71a428 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java @@ -1,13 +1,15 @@ package dev.cleat.api; import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @SpringBootTest(classes = CleatApiApplication.class) -@ActiveProfiles("test") @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@EnableJpaRepositories(basePackages = "dev.cleat") +@EntityScan(basePackages = "dev.cleat") public class CleatApiTests extends AbstractIntegrationTest { @Test diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index ec00f7f1..19f18ac4 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -1,13 +1,15 @@ package dev.cleat.worker; import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @SpringBootTest(classes = CleatWorkerApplication.class) -@ActiveProfiles("test") @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@EnableJpaRepositories(basePackages = "dev.cleat") +@EntityScan(basePackages = "dev.cleat") public class CleatWorkerApplicationTests extends AbstractIntegrationTest { @Test From 13430018ced0179350b9e76f99f0c2936cefcaa9 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 15:30:50 +0400 Subject: [PATCH 20/50] test: remove @EntityScan and @EnableJpaRepositories from integration tests --- .../apps/api/src/test/java/dev/cleat/api/CleatApiTests.java | 4 ---- .../java/dev/cleat/worker/CleatWorkerApplicationTests.java | 4 ---- 2 files changed, 8 deletions(-) diff --git a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java index 7c71a428..1ccae1c1 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java @@ -1,15 +1,11 @@ package dev.cleat.api; import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @SpringBootTest(classes = CleatApiApplication.class) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@EnableJpaRepositories(basePackages = "dev.cleat") -@EntityScan(basePackages = "dev.cleat") public class CleatApiTests extends AbstractIntegrationTest { @Test diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index 19f18ac4..9cba2be0 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -1,15 +1,11 @@ package dev.cleat.worker; import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @SpringBootTest(classes = CleatWorkerApplication.class) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@EnableJpaRepositories(basePackages = "dev.cleat") -@EntityScan(basePackages = "dev.cleat") public class CleatWorkerApplicationTests extends AbstractIntegrationTest { @Test From b71a4c5c16290560d7c8422d31d344a1232571ef Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 15:38:05 +0400 Subject: [PATCH 21/50] test: add @EntityScan and @EnableJpaRepositories to CleatWorkerApllicationTests --- .../java/dev/cleat/worker/CleatWorkerApplicationTests.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index 9cba2be0..4514d874 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -1,11 +1,15 @@ package dev.cleat.worker; import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @SpringBootTest(classes = CleatWorkerApplication.class) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@EntityScan(basePackages = "dev.cleat.persistence.entity") +@EnableJpaRepositories(basePackages = "dev.cleat.persistence.repository") public class CleatWorkerApplicationTests extends AbstractIntegrationTest { @Test From 31d74b2e5ffab530b5f11357f337d56d36408ebc Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 15:42:34 +0400 Subject: [PATCH 22/50] test: add @EntityScan and @EnableJpaRepositories(basePackages=dev.cleat) to CleatWorkerApllicationTests(basePackages=dev.cleat) --- .../java/dev/cleat/worker/CleatWorkerApplicationTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index 4514d874..697813fb 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -8,8 +8,8 @@ @SpringBootTest(classes = CleatWorkerApplication.class) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@EntityScan(basePackages = "dev.cleat.persistence.entity") -@EnableJpaRepositories(basePackages = "dev.cleat.persistence.repository") +@EntityScan(basePackages = "dev.cleat") +@EnableJpaRepositories(basePackages = "dev.cleat") public class CleatWorkerApplicationTests extends AbstractIntegrationTest { @Test From 3b0c90185ce46349c3a0baa211576155d859baab Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 16:08:18 +0400 Subject: [PATCH 23/50] fix(test): resolve Spring context wiring issues in multi-module build --- .../apps/api/src/test/java/dev/cleat/api/CleatApiTests.java | 6 +++++- .../java/dev/cleat/worker/CleatWorkerApplicationTests.java | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java index 1ccae1c1..9f344670 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java @@ -1,10 +1,14 @@ package dev.cleat.api; import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -@SpringBootTest(classes = CleatApiApplication.class) +@SpringBootTest +@EntityScan(basePackages = "dev.cleat.persistence.entity") +@EnableJpaRepositories(basePackages = "dev.cleat.persistence.repository") @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) public class CleatApiTests extends AbstractIntegrationTest { diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index 697813fb..b81dcced 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -7,9 +7,9 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @SpringBootTest(classes = CleatWorkerApplication.class) +@EntityScan(basePackages = "dev.cleat.persistence.entity") +@EnableJpaRepositories(basePackages = "dev.cleat.persistence.repository") @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@EntityScan(basePackages = "dev.cleat") -@EnableJpaRepositories(basePackages = "dev.cleat") public class CleatWorkerApplicationTests extends AbstractIntegrationTest { @Test From 2276feb09176f585c2b815cbfcfa1926173a5796 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 16:18:39 +0400 Subject: [PATCH 24/50] test: add properties parameter to @SpringBootTest --- .../apps/api/src/test/java/dev/cleat/api/CleatApiTests.java | 2 +- .../java/dev/cleat/worker/CleatWorkerApplicationTests.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java index 9f344670..d0c5aa3d 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java @@ -6,7 +6,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -@SpringBootTest +@SpringBootTest(properties = "spring.main.allow-bean-definition-overriding=true") @EntityScan(basePackages = "dev.cleat.persistence.entity") @EnableJpaRepositories(basePackages = "dev.cleat.persistence.repository") @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index b81dcced..9092a208 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -6,7 +6,9 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -@SpringBootTest(classes = CleatWorkerApplication.class) +@SpringBootTest( + classes = CleatWorkerApplication.class, + properties = "spring.main.allow-bean-definition-overriding=true") @EntityScan(basePackages = "dev.cleat.persistence.entity") @EnableJpaRepositories(basePackages = "dev.cleat.persistence.repository") @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) From 1ca2b28dee9783393bd4b03e418e7bb18b88d11b Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 16:23:33 +0400 Subject: [PATCH 25/50] test: add classes parameter to @SpringBootTest in CleatApiApplication --- backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java index d0c5aa3d..41cdf850 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java @@ -6,7 +6,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -@SpringBootTest(properties = "spring.main.allow-bean-definition-overriding=true") +@SpringBootTest(classes = CleatApiApplication.class, properties = "spring.main.allow-bean-definition-overriding=true") @EntityScan(basePackages = "dev.cleat.persistence.entity") @EnableJpaRepositories(basePackages = "dev.cleat.persistence.repository") @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) From 4fb6ea52635e8fe6d6e749b609a3845b66db0ed4 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 16:42:17 +0400 Subject: [PATCH 26/50] test: isolate test contexts via ComponentScan filters --- .../src/test/java/dev/cleat/api/CleatApiTests.java | 9 +++++---- .../cleat/worker/CleatWorkerApplicationTests.java | 13 ++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java index 41cdf850..7f49504f 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java @@ -1,15 +1,16 @@ package dev.cleat.api; import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.FilterType; @SpringBootTest(classes = CleatApiApplication.class, properties = "spring.main.allow-bean-definition-overriding=true") -@EntityScan(basePackages = "dev.cleat.persistence.entity") -@EnableJpaRepositories(basePackages = "dev.cleat.persistence.repository") @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@ComponentScan( + basePackages = "dev.cleat", + excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "dev.cleat.worker.*")) public class CleatApiTests extends AbstractIntegrationTest { @Test diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index 9092a208..205f2179 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -1,17 +1,16 @@ package dev.cleat.worker; import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.FilterType; -@SpringBootTest( - classes = CleatWorkerApplication.class, - properties = "spring.main.allow-bean-definition-overriding=true") -@EntityScan(basePackages = "dev.cleat.persistence.entity") -@EnableJpaRepositories(basePackages = "dev.cleat.persistence.repository") +@SpringBootTest(classes = CleatWorkerApplication.class) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@ComponentScan( + basePackages = "dev.cleat", + excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "dev.cleat.api.*")) public class CleatWorkerApplicationTests extends AbstractIntegrationTest { @Test From b8d0b5da943cf02604d9b96e7798fea5d4cc83e7 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 17:36:56 +0400 Subject: [PATCH 27/50] test: isolate test contexts with dedicated TestConfigs --- .../src/test/java/dev/cleat/api/CleatApiTests.java | 7 +------ .../src/test/java/dev/cleat/api/TestApiConfig.java | 12 ++++++++++++ .../cleat/worker/CleatWorkerApplicationTests.java | 7 +------ .../test/java/dev/cleat/worker/TestWorkerConfig.java | 12 ++++++++++++ 4 files changed, 26 insertions(+), 12 deletions(-) create mode 100644 backend/apps/api/src/test/java/dev/cleat/api/TestApiConfig.java create mode 100644 backend/apps/worker/src/test/java/dev/cleat/worker/TestWorkerConfig.java diff --git a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java index 7f49504f..9518ba21 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java @@ -3,14 +3,9 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.FilterType; -@SpringBootTest(classes = CleatApiApplication.class, properties = "spring.main.allow-bean-definition-overriding=true") +@SpringBootTest(classes = TestApiConfig.class) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@ComponentScan( - basePackages = "dev.cleat", - excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "dev.cleat.worker.*")) public class CleatApiTests extends AbstractIntegrationTest { @Test diff --git a/backend/apps/api/src/test/java/dev/cleat/api/TestApiConfig.java b/backend/apps/api/src/test/java/dev/cleat/api/TestApiConfig.java new file mode 100644 index 00000000..6a1fd0fa --- /dev/null +++ b/backend/apps/api/src/test/java/dev/cleat/api/TestApiConfig.java @@ -0,0 +1,12 @@ +package dev.cleat.api; + +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +@Configuration +@ComponentScan(basePackages = "dev.cleat.api") +@EnableJpaRepositories(basePackages = "dev.cleat.persistence.repository") +@EntityScan(basePackages = "dev.cleat.persistence.entity") +public class TestApiConfig {} diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index 205f2179..4df2fd1a 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -3,14 +3,9 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.FilterType; -@SpringBootTest(classes = CleatWorkerApplication.class) +@SpringBootTest(classes = TestWorkerConfig.class) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@ComponentScan( - basePackages = "dev.cleat", - excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "dev.cleat.api.*")) public class CleatWorkerApplicationTests extends AbstractIntegrationTest { @Test diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/TestWorkerConfig.java b/backend/apps/worker/src/test/java/dev/cleat/worker/TestWorkerConfig.java new file mode 100644 index 00000000..d3197806 --- /dev/null +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/TestWorkerConfig.java @@ -0,0 +1,12 @@ +package dev.cleat.worker; + +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +@Configuration +@ComponentScan(basePackages = "dev.cleat.worker") +@EnableJpaRepositories(basePackages = "dev.cleat.persistence.repository") +@EntityScan(basePackages = "dev.cleat.persistence.entity") +public class TestWorkerConfig {} From d8c1368334d75c36059b32ca8a619b9a36326c77 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 17:42:10 +0400 Subject: [PATCH 28/50] fix(tests): resolve bean conflicts with context isolation --- .../src/test/java/dev/cleat/api/CleatApiTests.java | 12 +++++++++--- .../cleat/worker/CleatWorkerApplicationTests.java | 12 +++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java index 9518ba21..5bab5bb4 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java @@ -1,11 +1,17 @@ package dev.cleat.api; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; -@SpringBootTest(classes = TestApiConfig.class) -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@SpringBootTest( + classes = {CleatApiApplication.class}, + webEnvironment = SpringBootTest.WebEnvironment.MOCK) +@TestPropertySource( + properties = { + "spring.main.allow-bean-definition-overriding=true", + "spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration" + }) public class CleatApiTests extends AbstractIntegrationTest { @Test diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index 4df2fd1a..6a0b6280 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -1,11 +1,17 @@ package dev.cleat.worker; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; -@SpringBootTest(classes = TestWorkerConfig.class) -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@SpringBootTest( + classes = {CleatWorkerApplication.class}, + webEnvironment = SpringBootTest.WebEnvironment.MOCK) +@TestPropertySource( + properties = { + "spring.main.allow-bean-definition-overriding=true", + "spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration" + }) public class CleatWorkerApplicationTests extends AbstractIntegrationTest { @Test From 3329cca0c53c7c3b5082589c43ecf3764420c580 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 17:53:17 +0400 Subject: [PATCH 29/50] fix(tests): resolve context contamination with DirtiesContext --- .../java/dev/cleat/api/AbstractIntegrationTest.java | 4 ++-- .../src/test/java/dev/cleat/api/CleatApiTests.java | 12 +++--------- .../cleat/worker/CleatWorkerApplicationTests.java | 12 +++--------- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/backend/apps/api/src/test/java/dev/cleat/api/AbstractIntegrationTest.java b/backend/apps/api/src/test/java/dev/cleat/api/AbstractIntegrationTest.java index de0c0302..6e90f948 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/AbstractIntegrationTest.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/AbstractIntegrationTest.java @@ -11,9 +11,9 @@ public abstract class AbstractIntegrationTest { @Container @ServiceConnection - static PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres:16-alpine"); + PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres:16-alpine"); @Container @ServiceConnection - static GenericContainer redis = new GenericContainer<>("redis:7-alpine").withExposedPorts(6379); + GenericContainer redis = new GenericContainer<>("redis:7-alpine").withExposedPorts(6379); } diff --git a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java index 5bab5bb4..98f0e225 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java @@ -2,16 +2,10 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.TestPropertySource; +import org.springframework.test.annotation.DirtiesContext; -@SpringBootTest( - classes = {CleatApiApplication.class}, - webEnvironment = SpringBootTest.WebEnvironment.MOCK) -@TestPropertySource( - properties = { - "spring.main.allow-bean-definition-overriding=true", - "spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration" - }) +@SpringBootTest(classes = CleatApiApplication.class) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) public class CleatApiTests extends AbstractIntegrationTest { @Test diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index 6a0b6280..3d980caf 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -2,16 +2,10 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.TestPropertySource; +import org.springframework.test.annotation.DirtiesContext; -@SpringBootTest( - classes = {CleatWorkerApplication.class}, - webEnvironment = SpringBootTest.WebEnvironment.MOCK) -@TestPropertySource( - properties = { - "spring.main.allow-bean-definition-overriding=true", - "spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration" - }) +@SpringBootTest(classes = CleatWorkerApplication.class) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) public class CleatWorkerApplicationTests extends AbstractIntegrationTest { @Test From b8267073de4cf904f78ae1081de9c0e17e826794 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 17:59:25 +0400 Subject: [PATCH 30/50] test: add webEnvironment parameter to @SpringBootTest --- backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java | 2 +- .../test/java/dev/cleat/worker/CleatWorkerApplicationTests.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java index 98f0e225..92f1c677 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java @@ -4,7 +4,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; -@SpringBootTest(classes = CleatApiApplication.class) +@SpringBootTest(classes = CleatApiApplication.class, webEnvironment = SpringBootTest.WebEnvironment.NONE) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) public class CleatApiTests extends AbstractIntegrationTest { diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index 3d980caf..5f240ff2 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -4,7 +4,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; -@SpringBootTest(classes = CleatWorkerApplication.class) +@SpringBootTest(classes = CleatWorkerApplication.class, webEnvironment = SpringBootTest.WebEnvironment.NONE) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) public class CleatWorkerApplicationTests extends AbstractIntegrationTest { From c6b1554ecfeba1b2df526d4b3006c9373cf69b20 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 18:05:20 +0400 Subject: [PATCH 31/50] test: add static modifier to containers in AbstractIntegrationTest --- .../src/test/java/dev/cleat/api/AbstractIntegrationTest.java | 4 ++-- .../apps/api/src/test/java/dev/cleat/api/CleatApiTests.java | 2 +- .../java/dev/cleat/worker/CleatWorkerApplicationTests.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/apps/api/src/test/java/dev/cleat/api/AbstractIntegrationTest.java b/backend/apps/api/src/test/java/dev/cleat/api/AbstractIntegrationTest.java index 6e90f948..de0c0302 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/AbstractIntegrationTest.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/AbstractIntegrationTest.java @@ -11,9 +11,9 @@ public abstract class AbstractIntegrationTest { @Container @ServiceConnection - PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres:16-alpine"); + static PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres:16-alpine"); @Container @ServiceConnection - GenericContainer redis = new GenericContainer<>("redis:7-alpine").withExposedPorts(6379); + static GenericContainer redis = new GenericContainer<>("redis:7-alpine").withExposedPorts(6379); } diff --git a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java index 92f1c677..98f0e225 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java @@ -4,7 +4,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; -@SpringBootTest(classes = CleatApiApplication.class, webEnvironment = SpringBootTest.WebEnvironment.NONE) +@SpringBootTest(classes = CleatApiApplication.class) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) public class CleatApiTests extends AbstractIntegrationTest { diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index 5f240ff2..3d980caf 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -4,7 +4,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; -@SpringBootTest(classes = CleatWorkerApplication.class, webEnvironment = SpringBootTest.WebEnvironment.NONE) +@SpringBootTest(classes = CleatWorkerApplication.class) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) public class CleatWorkerApplicationTests extends AbstractIntegrationTest { From f4b3ace0e2b9a5cd913d800d74c8582d5e4c1a83 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 21:00:36 +0400 Subject: [PATCH 32/50] fix(spring-context): scope scanBasePackages in api and worker apps --- .../main/java/dev/cleat/api/CleatApiApplication.java | 9 ++++++++- .../src/test/java/dev/cleat/api/TestApiConfig.java | 12 ------------ .../dev/cleat/worker/CleatWorkerApplication.java | 3 ++- .../test/java/dev/cleat/worker/TestWorkerConfig.java | 12 ------------ 4 files changed, 10 insertions(+), 26 deletions(-) delete mode 100644 backend/apps/api/src/test/java/dev/cleat/api/TestApiConfig.java delete mode 100644 backend/apps/worker/src/test/java/dev/cleat/worker/TestWorkerConfig.java diff --git a/backend/apps/api/src/main/java/dev/cleat/api/CleatApiApplication.java b/backend/apps/api/src/main/java/dev/cleat/api/CleatApiApplication.java index 8fdd8836..b5f0ddab 100644 --- a/backend/apps/api/src/main/java/dev/cleat/api/CleatApiApplication.java +++ b/backend/apps/api/src/main/java/dev/cleat/api/CleatApiApplication.java @@ -5,7 +5,14 @@ import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -@SpringBootApplication(scanBasePackages = {"dev.cleat"}) +@SpringBootApplication( + scanBasePackages = { + "dev.cleat.api", + "dev.cleat.persistence", + "dev.cleat.domain", + "dev.cleat.common", + "dev.cleat.scanning" + }) @EnableJpaRepositories(basePackages = "dev.cleat.persistence.repository") @EntityScan(basePackages = "dev.cleat.persistence.entity") public class CleatApiApplication { diff --git a/backend/apps/api/src/test/java/dev/cleat/api/TestApiConfig.java b/backend/apps/api/src/test/java/dev/cleat/api/TestApiConfig.java deleted file mode 100644 index 6a1fd0fa..00000000 --- a/backend/apps/api/src/test/java/dev/cleat/api/TestApiConfig.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.cleat.api; - -import org.springframework.boot.autoconfigure.domain.EntityScan; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; - -@Configuration -@ComponentScan(basePackages = "dev.cleat.api") -@EnableJpaRepositories(basePackages = "dev.cleat.persistence.repository") -@EntityScan(basePackages = "dev.cleat.persistence.entity") -public class TestApiConfig {} diff --git a/backend/apps/worker/src/main/java/dev/cleat/worker/CleatWorkerApplication.java b/backend/apps/worker/src/main/java/dev/cleat/worker/CleatWorkerApplication.java index a5554749..880fba9f 100644 --- a/backend/apps/worker/src/main/java/dev/cleat/worker/CleatWorkerApplication.java +++ b/backend/apps/worker/src/main/java/dev/cleat/worker/CleatWorkerApplication.java @@ -4,7 +4,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; -@SpringBootApplication(scanBasePackages = {"dev.cleat.persistence", "dev.cleat.scanning", "dev.cleat.worker"}) +@SpringBootApplication( + scanBasePackages = {"dev.cleat.persistence", "dev.cleat.scanning", "dev.cleat.worker", "dev.cleat.domain"}) @EnableScheduling public class CleatWorkerApplication { diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/TestWorkerConfig.java b/backend/apps/worker/src/test/java/dev/cleat/worker/TestWorkerConfig.java deleted file mode 100644 index d3197806..00000000 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/TestWorkerConfig.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.cleat.worker; - -import org.springframework.boot.autoconfigure.domain.EntityScan; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; - -@Configuration -@ComponentScan(basePackages = "dev.cleat.worker") -@EnableJpaRepositories(basePackages = "dev.cleat.persistence.repository") -@EntityScan(basePackages = "dev.cleat.persistence.entity") -public class TestWorkerConfig {} From 77f18763a7cf2555b9f2996c86c1cefae33567c5 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 21:06:38 +0400 Subject: [PATCH 33/50] fix(spring-context): add dev.cleat.common package to @SpringBootApplication in worker apps --- .../java/dev/cleat/worker/CleatWorkerApplication.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/apps/worker/src/main/java/dev/cleat/worker/CleatWorkerApplication.java b/backend/apps/worker/src/main/java/dev/cleat/worker/CleatWorkerApplication.java index 880fba9f..927e2cde 100644 --- a/backend/apps/worker/src/main/java/dev/cleat/worker/CleatWorkerApplication.java +++ b/backend/apps/worker/src/main/java/dev/cleat/worker/CleatWorkerApplication.java @@ -5,7 +5,13 @@ import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication( - scanBasePackages = {"dev.cleat.persistence", "dev.cleat.scanning", "dev.cleat.worker", "dev.cleat.domain"}) + scanBasePackages = { + "dev.cleat.persistence", + "dev.cleat.scanning", + "dev.cleat.worker", + "dev.cleat.domain", + "dev.cleat.common" + }) @EnableScheduling public class CleatWorkerApplication { From dfa24515c90b537c287fcd8547f598688910aa64 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 21:13:50 +0400 Subject: [PATCH 34/50] fix(spring-context): add @EntityScan and @EnableJpaRepositories to CleatWorkerApplication in worker module --- .../main/java/dev/cleat/worker/CleatWorkerApplication.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/apps/worker/src/main/java/dev/cleat/worker/CleatWorkerApplication.java b/backend/apps/worker/src/main/java/dev/cleat/worker/CleatWorkerApplication.java index 927e2cde..8ebbdb3b 100644 --- a/backend/apps/worker/src/main/java/dev/cleat/worker/CleatWorkerApplication.java +++ b/backend/apps/worker/src/main/java/dev/cleat/worker/CleatWorkerApplication.java @@ -2,6 +2,8 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication( @@ -12,6 +14,8 @@ "dev.cleat.domain", "dev.cleat.common" }) +@EnableJpaRepositories(basePackages = "dev.cleat.persistence.repository") +@EntityScan(basePackages = "dev.cleat.persistence.entity") @EnableScheduling public class CleatWorkerApplication { From 09815e4698fa8b5f444c33ae1615a6d0b862d730 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 21:41:12 +0400 Subject: [PATCH 35/50] refactor(exception): improve security and logging in GlobalExceptionHandler --- .../dev/cleat/api/exception/GlobalExceptionHandler.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/apps/api/src/main/java/dev/cleat/api/exception/GlobalExceptionHandler.java b/backend/apps/api/src/main/java/dev/cleat/api/exception/GlobalExceptionHandler.java index 71adf3e5..b7cca016 100644 --- a/backend/apps/api/src/main/java/dev/cleat/api/exception/GlobalExceptionHandler.java +++ b/backend/apps/api/src/main/java/dev/cleat/api/exception/GlobalExceptionHandler.java @@ -2,6 +2,8 @@ import dev.cleat.common.exception.NotFoundException; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -9,15 +11,18 @@ @RestControllerAdvice public class GlobalExceptionHandler { + private static final Logger LOG = LoggerFactory.getLogger(GlobalExceptionHandler.class); @ExceptionHandler(NotFoundException.class) public ResponseEntity> handleNotFound(NotFoundException ex) { + LOG.info("Resource not found: {}", ex.getMessage()); return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of("error", ex.getMessage())); } @ExceptionHandler(RuntimeException.class) public ResponseEntity> handleRuntimeException(RuntimeException ex) { + LOG.error("Unhandled exception occured: ", ex); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(Map.of("error", "An unexpected error occured: " + ex.getMessage())); + .body(Map.of("error", "An unexpected error occured")); } } From 6dcca8379d30d4beca3120186ecd8dfa7995eb09 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 21:56:03 +0400 Subject: [PATCH 36/50] refactor(dto): remove 'priority' field from VulnerabilityRequestDto to prevent client manipulation --- .../common/dto/request/VulnerabilityRequestDto.java | 13 ------------- .../persistence/mapper/VulnerabilityMapper.java | 1 - ..._table.sql => V3__alter_vulnerability_table.sql} | 0 3 files changed, 14 deletions(-) rename backend/libs/persistence/src/main/resources/db/migration/{V3_alter_vulnerability_table.sql => V3__alter_vulnerability_table.sql} (100%) diff --git a/backend/libs/common/src/main/java/dev/cleat/common/dto/request/VulnerabilityRequestDto.java b/backend/libs/common/src/main/java/dev/cleat/common/dto/request/VulnerabilityRequestDto.java index 1ca50ebb..ebb420fe 100644 --- a/backend/libs/common/src/main/java/dev/cleat/common/dto/request/VulnerabilityRequestDto.java +++ b/backend/libs/common/src/main/java/dev/cleat/common/dto/request/VulnerabilityRequestDto.java @@ -1,6 +1,5 @@ package dev.cleat.common.dto.request; -import dev.cleat.common.enums.Priority; import dev.cleat.common.enums.Reachable; import dev.cleat.common.enums.Severity; import java.util.List; @@ -14,7 +13,6 @@ public class VulnerabilityRequestDto { private String fixedVersion; private Double cvss; private Severity severity; - private Priority priority; private Double epss; private Boolean kev; private Reachable reachable; @@ -33,7 +31,6 @@ public VulnerabilityRequestDto( String fixedVersion, Double cvss, Severity severity, - Priority priority, Double epss, Boolean kev, Reachable reachable, @@ -49,7 +46,6 @@ public VulnerabilityRequestDto( this.fixedVersion = fixedVersion; this.cvss = cvss; this.severity = severity; - this.priority = priority; this.epss = epss; this.kev = kev; this.reachable = reachable; @@ -114,15 +110,6 @@ public VulnerabilityRequestDto setSeverity(Severity severity) { return this; } - public Priority getPriority() { - return priority; - } - - public VulnerabilityRequestDto setPriority(Priority priority) { - this.priority = priority; - return this; - } - public Double getEpss() { return epss; } diff --git a/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/VulnerabilityMapper.java b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/VulnerabilityMapper.java index dc8f8d0a..52906cb0 100644 --- a/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/VulnerabilityMapper.java +++ b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/VulnerabilityMapper.java @@ -40,7 +40,6 @@ public VulnerabilityEntity toVulnerabilityEntity(VulnerabilityRequestDto vulnera .setFixedVersion(vulnerabilityRequestDto.getFixedVersion()) .setCvss(vulnerabilityRequestDto.getCvss()) .setSeverity(vulnerabilityRequestDto.getSeverity()) - .setPriority(vulnerabilityRequestDto.getPriority()) .setEpss(vulnerabilityRequestDto.getEpss()) .setKev(vulnerabilityRequestDto.getKev()) .setReachable(vulnerabilityRequestDto.getReachable()) diff --git a/backend/libs/persistence/src/main/resources/db/migration/V3_alter_vulnerability_table.sql b/backend/libs/persistence/src/main/resources/db/migration/V3__alter_vulnerability_table.sql similarity index 100% rename from backend/libs/persistence/src/main/resources/db/migration/V3_alter_vulnerability_table.sql rename to backend/libs/persistence/src/main/resources/db/migration/V3__alter_vulnerability_table.sql From 460951d50fb1b7b3102e759b52f9f10e60ef9a3c Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 22:16:48 +0400 Subject: [PATCH 37/50] refactor(domain): add canonical contructor to Vulnerability record to enforce non-null cvss and severity --- .../main/java/dev/cleat/domain/model/Vulnerability.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/backend/libs/domain/src/main/java/dev/cleat/domain/model/Vulnerability.java b/backend/libs/domain/src/main/java/dev/cleat/domain/model/Vulnerability.java index 36c1e1fe..ee40c004 100644 --- a/backend/libs/domain/src/main/java/dev/cleat/domain/model/Vulnerability.java +++ b/backend/libs/domain/src/main/java/dev/cleat/domain/model/Vulnerability.java @@ -1,5 +1,12 @@ package dev.cleat.domain.model; import dev.cleat.common.enums.Severity; +import java.util.Objects; -public record Vulnerability(Boolean kev, Double cvss, Severity severity) {} +public record Vulnerability(Boolean kev, Double cvss, Severity severity) { + public Vulnerability { + Objects.requireNonNull(cvss, "cvss cannot be null"); + Objects.requireNonNull(severity, "severity cannot be null"); + kev = (kev != null) ? kev : false; + } +} From 5e052902627f85441ad683b3e8794ff423de00ee Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 22:30:56 +0400 Subject: [PATCH 38/50] fix(test): update test data in VulnerabilityScannerTest to provide valid,non-null arguments for Vulnerability constructor --- .../test/java/dev/cleat/scanning/VulnerabilityScannerTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java b/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java index ddb8ac0d..91d4121d 100644 --- a/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java +++ b/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java @@ -7,6 +7,7 @@ import static org.mockito.Mockito.when; import dev.cleat.common.enums.Priority; +import dev.cleat.common.enums.Severity; import dev.cleat.domain.PriorityCalculator; import dev.cleat.persistence.entity.VulnerabilityEntity; import dev.cleat.persistence.repository.VulnerabilityRepository; @@ -34,7 +35,7 @@ void givenVulnerabilityWithKevAndHighCvssWhenProcessingThenPriorityShouldBeUrgen // given UUID uuid = UUID.randomUUID(); VulnerabilityEntity vulnerabilityEntity = - new VulnerabilityEntity().setId(uuid).setKev(true).setCvss(9.5); + new VulnerabilityEntity().setId(uuid).setKev(true).setCvss(9.5).setSeverity(Severity.HIGH); when(vulnerabilityRepository.findById(uuid)).thenReturn(Optional.of(vulnerabilityEntity)); From a2cd900ba725a26c886b3d7f541afefe026bc06f Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 22:42:44 +0400 Subject: [PATCH 39/50] fix(test): provide mandatory 'cvss','kev' and 'severity' values in test entities to satisfy Vulnerability constructor constraints --- .../test/java/dev/cleat/scanning/VulnerabilityScannerTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java b/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java index 91d4121d..7dfe9c1f 100644 --- a/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java +++ b/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java @@ -58,7 +58,8 @@ void givenVulnerabilityWithKevAndHighCvssWhenProcessingThenPriorityShouldBeUrgen void whenCvssIsInvalidThenShouldThrowException() { // given - VulnerabilityEntity vulnerabilityEntity = new VulnerabilityEntity().setCvss(15.0); + VulnerabilityEntity vulnerabilityEntity = new VulnerabilityEntity().setKev(true).setCvss(15.0) + c.setSeverity(Severity.HIGH); when(priorityCalculator.calculate(any())).thenThrow(new IllegalArgumentException("Invalid CVSS")); From d348d0284ca4fdb624ba66209829a82e624ee44b Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Mon, 29 Jun 2026 22:49:44 +0400 Subject: [PATCH 40/50] fix(scanning): correct typo causing compilation error --- .../java/dev/cleat/scanning/VulnerabilityScannerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java b/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java index 7dfe9c1f..e3735d0b 100644 --- a/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java +++ b/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java @@ -58,8 +58,8 @@ void givenVulnerabilityWithKevAndHighCvssWhenProcessingThenPriorityShouldBeUrgen void whenCvssIsInvalidThenShouldThrowException() { // given - VulnerabilityEntity vulnerabilityEntity = new VulnerabilityEntity().setKev(true).setCvss(15.0) - c.setSeverity(Severity.HIGH); + VulnerabilityEntity vulnerabilityEntity = + new VulnerabilityEntity().setKev(true).setCvss(15.0).setSeverity(Severity.HIGH); when(priorityCalculator.calculate(any())).thenThrow(new IllegalArgumentException("Invalid CVSS")); From 11be14a648a29722182e47f082d4bdeb53c239cf Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Tue, 30 Jun 2026 01:11:30 +0400 Subject: [PATCH 41/50] refactor(persistence): add @PrePersist to populate detectedAt automatically --- .../main/java/dev/cleat/domain/PriorityCalculator.java | 2 +- .../cleat/persistence/entity/CodeScanAlertEntity.java | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/backend/libs/domain/src/main/java/dev/cleat/domain/PriorityCalculator.java b/backend/libs/domain/src/main/java/dev/cleat/domain/PriorityCalculator.java index 5111772a..95018fa3 100644 --- a/backend/libs/domain/src/main/java/dev/cleat/domain/PriorityCalculator.java +++ b/backend/libs/domain/src/main/java/dev/cleat/domain/PriorityCalculator.java @@ -11,7 +11,7 @@ public Priority calculate(Vulnerability vulnerability) { if (vulnerability.cvss() < 0 || vulnerability.cvss() > 10) { throw new IllegalArgumentException("CVSS must be between 0 and 10"); } - if (Boolean.TRUE.equals(vulnerability.kev()) || vulnerability.cvss() != null && vulnerability.cvss() >= 9.0) { + if (vulnerability.kev() || vulnerability.cvss() >= 9.0) { return Priority.URGENT; } diff --git a/backend/libs/persistence/src/main/java/dev/cleat/persistence/entity/CodeScanAlertEntity.java b/backend/libs/persistence/src/main/java/dev/cleat/persistence/entity/CodeScanAlertEntity.java index fe633e22..800ff30d 100644 --- a/backend/libs/persistence/src/main/java/dev/cleat/persistence/entity/CodeScanAlertEntity.java +++ b/backend/libs/persistence/src/main/java/dev/cleat/persistence/entity/CodeScanAlertEntity.java @@ -9,6 +9,7 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.PrePersist; import jakarta.persistence.Table; import java.time.OffsetDateTime; import java.util.UUID; @@ -53,7 +54,7 @@ public class CodeScanAlertEntity { @Column(name = "tool") private String tool; - @Column(name = "detected_at") + @Column(name = "detected_at", insertable = false, updatable = true) private OffsetDateTime detectedAt; @Column(name = "description") @@ -206,4 +207,11 @@ public CodeScanAlertEntity setDescription(String description) { this.description = description; return this; } + + @PrePersist + protected void onCreate() { + if (this.detectedAt == null) { + this.detectedAt = OffsetDateTime.now(); + } + } } From dc213936695ed690af398a4712d07a240154133d Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Tue, 30 Jun 2026 01:34:55 +0400 Subject: [PATCH 42/50] refactor(api): improve exception handling in GlobalExceptionHandler --- .../cleat/api/exception/GlobalExceptionHandler.java | 9 +++++++-- .../cleat/persistence/mapper/ActivityEventMapper.java | 5 ++++- .../java/dev/cleat/persistence/mapper/UsageMapper.java | 10 +++++++--- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/backend/apps/api/src/main/java/dev/cleat/api/exception/GlobalExceptionHandler.java b/backend/apps/api/src/main/java/dev/cleat/api/exception/GlobalExceptionHandler.java index b7cca016..1dc0dd36 100644 --- a/backend/apps/api/src/main/java/dev/cleat/api/exception/GlobalExceptionHandler.java +++ b/backend/apps/api/src/main/java/dev/cleat/api/exception/GlobalExceptionHandler.java @@ -8,6 +8,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.server.ResponseStatusException; @RestControllerAdvice public class GlobalExceptionHandler { @@ -19,8 +20,12 @@ public ResponseEntity> handleNotFound(NotFoundException ex) return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of("error", ex.getMessage())); } - @ExceptionHandler(RuntimeException.class) - public ResponseEntity> handleRuntimeException(RuntimeException ex) { + @ExceptionHandler(Exception.class) + public ResponseEntity> handleGeneralException(Exception ex) { + + if (ex instanceof ResponseStatusException) { + throw (ResponseStatusException) ex; + } LOG.error("Unhandled exception occured: ", ex); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(Map.of("error", "An unexpected error occured")); diff --git a/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/ActivityEventMapper.java b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/ActivityEventMapper.java index 4c509778..e6034a5c 100644 --- a/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/ActivityEventMapper.java +++ b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/ActivityEventMapper.java @@ -19,7 +19,10 @@ public ActivityEventResponseDto toActivityEventDto(ActivityEventEntity activityE .setSeverity(activityEventEntity.getSeverity()) .setActor(activityEventEntity.getActor()) .setTarget(activityEventEntity.getTarget()) - .setRepo(activityEventEntity.getRepo().getName()) + .setRepo( + activityEventEntity.getRepo() != null + ? activityEventEntity.getRepo().getName() + : "Unknown") .setMessage(activityEventEntity.getMessage()) .setCreatedAt(activityEventEntity.getCreatedAt()); } diff --git a/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/UsageMapper.java b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/UsageMapper.java index 1f836dad..42e43ee5 100644 --- a/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/UsageMapper.java +++ b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/UsageMapper.java @@ -2,6 +2,7 @@ import dev.cleat.common.dto.response.UsageResponseDto; import dev.cleat.persistence.entity.UsageEntity; +import java.util.Collections; import org.springframework.stereotype.Component; @Component @@ -25,8 +26,11 @@ public UsageResponseDto toUsageDto(UsageEntity usageEntity) { .setMonthlyCost(usageEntity.getMonthlyCost()) .setReclaimable(usageEntity.getReclaimable()) .setBreakdown(usageEntity.getBreakdown()) - .setSeries(usageEntity.getSeries().stream() - .map(usagePointMapper::toUsagePointDto) - .toList()); + .setSeries( + usageEntity.getSeries() != null + ? usageEntity.getSeries().stream() + .map(usagePointMapper::toUsagePointDto) + .toList() + : Collections.emptyList()); } } From e06aac23317cc2ca4b49b35eac4d385ee28cc11c Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Tue, 30 Jun 2026 02:15:29 +0400 Subject: [PATCH 43/50] refactor(migration): remove DEFAULT 'main' from code_scan_alerts.branch column --- .../dto/request/SecretFindingRequestDto.java | 25 +++++++++++++++++++ .../mapper/SecretFindingMapper.java | 6 ++++- .../V4__create_code_scan_alerts_table.sql | 2 +- .../cleat/scanning/CodeScanAlertScanner.java | 8 +----- .../service/CodeScanAlertService.java | 25 +++++++++++++++++-- .../service/SecretFindingService.java | 16 ++++++++++-- 6 files changed, 69 insertions(+), 13 deletions(-) diff --git a/backend/libs/common/src/main/java/dev/cleat/common/dto/request/SecretFindingRequestDto.java b/backend/libs/common/src/main/java/dev/cleat/common/dto/request/SecretFindingRequestDto.java index a458c31f..189f93d5 100644 --- a/backend/libs/common/src/main/java/dev/cleat/common/dto/request/SecretFindingRequestDto.java +++ b/backend/libs/common/src/main/java/dev/cleat/common/dto/request/SecretFindingRequestDto.java @@ -3,8 +3,11 @@ import dev.cleat.common.enums.Severity; import dev.cleat.common.enums.Validity; import java.time.OffsetDateTime; +import java.util.UUID; public class SecretFindingRequestDto { + private UUID accountId; + private UUID repoId; private String provider; private String secretType; private String file; @@ -19,6 +22,8 @@ public class SecretFindingRequestDto { public SecretFindingRequestDto() {} public SecretFindingRequestDto( + UUID accountId, + UUID repoId, String provider, String secretType, String file, @@ -30,6 +35,8 @@ public SecretFindingRequestDto( Severity severity, Boolean pushProtectionBlocked) { + this.accountId = accountId; + this.repoId = repoId; this.provider = provider; this.secretType = secretType; this.file = file; @@ -42,6 +49,24 @@ public SecretFindingRequestDto( this.pushProtectionBlocked = pushProtectionBlocked; } + public UUID getAccountId() { + return accountId; + } + + public SecretFindingRequestDto setAccountId(UUID accountId) { + this.accountId = accountId; + return this; + } + + public UUID getRepoId() { + return repoId; + } + + public SecretFindingRequestDto setRepoId(UUID repoId) { + this.repoId = repoId; + return this; + } + public String getProvider() { return provider; } diff --git a/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/SecretFindingMapper.java b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/SecretFindingMapper.java index b0fb8de0..a220b8ac 100644 --- a/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/SecretFindingMapper.java +++ b/backend/libs/persistence/src/main/java/dev/cleat/persistence/mapper/SecretFindingMapper.java @@ -12,7 +12,10 @@ public SecretFindingResponseDto toSecretFindingDto(SecretFindingEntity secretFin return new SecretFindingResponseDto() .setId(secretFindingEntity.getId()) .setAccountId(secretFindingEntity.getAccountId()) - .setRepo(secretFindingEntity.getRepo().getName()) + .setRepo( + secretFindingEntity.getRepo() != null + ? secretFindingEntity.getRepo().getName() + : "Unknown") .setProvider(secretFindingEntity.getProvider()) .setSecretType(secretFindingEntity.getSecretType()) .setFile(secretFindingEntity.getFile()) @@ -30,6 +33,7 @@ public SecretFindingEntity toSecretFindingEntity(SecretFindingRequestDto secretF return null; } return new SecretFindingEntity() + .setAccountId(secretFindingRequestDto.getAccountId()) .setProvider(secretFindingRequestDto.getProvider()) .setSecretType(secretFindingRequestDto.getSecretType()) .setFile(secretFindingRequestDto.getFile()) diff --git a/backend/libs/persistence/src/main/resources/db/migration/V4__create_code_scan_alerts_table.sql b/backend/libs/persistence/src/main/resources/db/migration/V4__create_code_scan_alerts_table.sql index 16636319..308edbea 100644 --- a/backend/libs/persistence/src/main/resources/db/migration/V4__create_code_scan_alerts_table.sql +++ b/backend/libs/persistence/src/main/resources/db/migration/V4__create_code_scan_alerts_table.sql @@ -4,7 +4,7 @@ CREATE TABLE code_scan_alerts( severity VARCHAR(50) NOT NULL, file VARCHAR(255) NOT NULL, line INTEGER CHECK (line>=0), - branch VARCHAR(250) DEFAULT 'main', + branch VARCHAR(250) DEFAULT, status VARCHAR(50) NOT NULL , tool VARCHAR(255) NOT NULL, detected_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, diff --git a/backend/libs/scanning/src/main/java/dev/cleat/scanning/CodeScanAlertScanner.java b/backend/libs/scanning/src/main/java/dev/cleat/scanning/CodeScanAlertScanner.java index b7f2a34d..0d452d06 100644 --- a/backend/libs/scanning/src/main/java/dev/cleat/scanning/CodeScanAlertScanner.java +++ b/backend/libs/scanning/src/main/java/dev/cleat/scanning/CodeScanAlertScanner.java @@ -3,7 +3,6 @@ import dev.cleat.common.enums.Severity; import dev.cleat.common.enums.Status; import dev.cleat.persistence.entity.CodeScanAlertEntity; -import dev.cleat.persistence.repository.CodeScanAlertRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -11,11 +10,6 @@ @Service public class CodeScanAlertScanner { private static final Logger LOG = LoggerFactory.getLogger(CodeScanAlertScanner.class); - private final CodeScanAlertRepository codeScanAlertRepository; - - public CodeScanAlertScanner(CodeScanAlertRepository codeScanAlertRepository) { - this.codeScanAlertRepository = codeScanAlertRepository; - } public void process(CodeScanAlertEntity codeScanAlertEntity) { LOG.info("Analyzing code scan alert for rule: {}", codeScanAlertEntity.getRule()); @@ -25,7 +19,7 @@ public void process(CodeScanAlertEntity codeScanAlertEntity) { } else { codeScanAlertEntity.setStatus(Status.OPEN); } - codeScanAlertRepository.save(codeScanAlertEntity); + LOG.info("Code scan alert processed successfully with status: {}", codeScanAlertEntity.getStatus()); } } diff --git a/backend/libs/scanning/src/main/java/dev/cleat/scanning/service/CodeScanAlertService.java b/backend/libs/scanning/src/main/java/dev/cleat/scanning/service/CodeScanAlertService.java index c0b5649b..3edef352 100644 --- a/backend/libs/scanning/src/main/java/dev/cleat/scanning/service/CodeScanAlertService.java +++ b/backend/libs/scanning/src/main/java/dev/cleat/scanning/service/CodeScanAlertService.java @@ -2,8 +2,12 @@ import dev.cleat.common.dto.request.CodeScanAlertRequestDto; import dev.cleat.common.dto.response.CodeScanAlertResponseDto; +import dev.cleat.common.exception.NotFoundException; import dev.cleat.persistence.entity.CodeScanAlertEntity; +import dev.cleat.persistence.entity.RepoEntity; import dev.cleat.persistence.mapper.CodeScanAlertMapper; +import dev.cleat.persistence.repository.CodeScanAlertRepository; +import dev.cleat.persistence.repository.RepoRepository; import dev.cleat.scanning.CodeScanAlertScanner; import org.springframework.stereotype.Service; @@ -12,17 +16,34 @@ public class CodeScanAlertService { private final CodeScanAlertMapper codeScanAlertMapper; private final CodeScanAlertScanner codeScanAlertScanner; + private final CodeScanAlertRepository codeScanAlertRepository; + private final RepoRepository repoRepository; - public CodeScanAlertService(CodeScanAlertMapper codeScanAlertMapper, CodeScanAlertScanner codeScanAlertScanner) { + public CodeScanAlertService( + CodeScanAlertMapper codeScanAlertMapper, + CodeScanAlertScanner codeScanAlertScanner, + CodeScanAlertRepository codeScanAlertRepository, + RepoRepository repoRepository) { this.codeScanAlertMapper = codeScanAlertMapper; this.codeScanAlertScanner = codeScanAlertScanner; + this.codeScanAlertRepository = codeScanAlertRepository; + this.repoRepository = repoRepository; } public CodeScanAlertResponseDto create(CodeScanAlertRequestDto codeScanAlertRequestDto) { CodeScanAlertEntity codeScanAlertEntity = codeScanAlertMapper.toCodeScanAlertEntity(codeScanAlertRequestDto); + if (codeScanAlertEntity.getBranch() == null + || codeScanAlertEntity.getBranch().isBlank()) { + RepoEntity repoEntity = repoRepository + .findById(codeScanAlertRequestDto.getRepo()) + .orElseThrow(() -> new NotFoundException("Repo not found")); + + codeScanAlertEntity.setBranch(repoEntity.getDefaultBranch()); + } codeScanAlertScanner.process(codeScanAlertEntity); - return codeScanAlertMapper.toCodeScanAlertDto(codeScanAlertEntity); + CodeScanAlertEntity savedEntity = codeScanAlertRepository.save(codeScanAlertEntity); + return codeScanAlertMapper.toCodeScanAlertDto(savedEntity); } } diff --git a/backend/libs/scanning/src/main/java/dev/cleat/scanning/service/SecretFindingService.java b/backend/libs/scanning/src/main/java/dev/cleat/scanning/service/SecretFindingService.java index c00148b0..f9cc3f36 100644 --- a/backend/libs/scanning/src/main/java/dev/cleat/scanning/service/SecretFindingService.java +++ b/backend/libs/scanning/src/main/java/dev/cleat/scanning/service/SecretFindingService.java @@ -2,8 +2,11 @@ import dev.cleat.common.dto.request.SecretFindingRequestDto; import dev.cleat.common.dto.response.SecretFindingResponseDto; +import dev.cleat.common.exception.NotFoundException; +import dev.cleat.persistence.entity.RepoEntity; import dev.cleat.persistence.entity.SecretFindingEntity; import dev.cleat.persistence.mapper.SecretFindingMapper; +import dev.cleat.persistence.repository.RepoRepository; import dev.cleat.scanning.SecretFindingScanner; import org.springframework.stereotype.Service; @@ -12,16 +15,25 @@ public class SecretFindingService { private final SecretFindingMapper secretFindingMapper; private final SecretFindingScanner secretScanner; + private final RepoRepository repoRepository; - public SecretFindingService(SecretFindingMapper secretFindingMapper, SecretFindingScanner secretScanner) { + public SecretFindingService( + SecretFindingMapper secretFindingMapper, + SecretFindingScanner secretScanner, + RepoRepository repoRepository) { this.secretFindingMapper = secretFindingMapper; this.secretScanner = secretScanner; + this.repoRepository = repoRepository; } public SecretFindingResponseDto create(SecretFindingRequestDto secretFindingRequestDto) { SecretFindingEntity secretFindingEntity = secretFindingMapper.toSecretFindingEntity(secretFindingRequestDto); - + RepoEntity repoEntity = repoRepository + .findById(secretFindingRequestDto.getRepoId()) + .orElseThrow(() -> new NotFoundException("Repo not found")); + secretFindingEntity.setAccountId(secretFindingRequestDto.getAccountId()); + secretFindingEntity.setRepo(repoEntity); secretScanner.process(secretFindingEntity); return secretFindingMapper.toSecretFindingDto(secretFindingEntity); From 4c0de2dd643b0c209310093780cbe6d884b68373 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Tue, 30 Jun 2026 02:38:50 +0400 Subject: [PATCH 44/50] fix(migartion): remove DEFAULT from code_scan_alerts.branch column --- .../java/dev/cleat/worker/VulnerabilityWorker.java | 11 ++++++++++- .../migration/V4__create_code_scan_alerts_table.sql | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/backend/apps/worker/src/main/java/dev/cleat/worker/VulnerabilityWorker.java b/backend/apps/worker/src/main/java/dev/cleat/worker/VulnerabilityWorker.java index 55be546a..8b3cc3f2 100644 --- a/backend/apps/worker/src/main/java/dev/cleat/worker/VulnerabilityWorker.java +++ b/backend/apps/worker/src/main/java/dev/cleat/worker/VulnerabilityWorker.java @@ -2,6 +2,8 @@ import dev.cleat.persistence.repository.VulnerabilityRepository; import dev.cleat.scanning.VulnerabilityScanner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -9,6 +11,7 @@ public class VulnerabilityWorker { private final VulnerabilityScanner vulnerabilityScanner; private final VulnerabilityRepository vulnerabilityRepository; + private static final Logger LOG = LoggerFactory.getLogger(VulnerabilityWorker.class); public VulnerabilityWorker( VulnerabilityScanner vulnerabilityScanner, VulnerabilityRepository vulnerabilityRepository) { @@ -18,6 +21,12 @@ public VulnerabilityWorker( @Scheduled(fixedDelay = 600000) public void runSecurityScan() { - vulnerabilityRepository.findAll().forEach(vulnerabilityScanner::processAndSave); + vulnerabilityRepository.findAll().forEach(vulnerability -> { + try { + vulnerabilityScanner.processAndSave(vulnerability); + } catch (Exception ex) { + LOG.error("Failed to process vulnerability ID: {}.Skipping...", vulnerability.getId(), ex); + } + }); } } diff --git a/backend/libs/persistence/src/main/resources/db/migration/V4__create_code_scan_alerts_table.sql b/backend/libs/persistence/src/main/resources/db/migration/V4__create_code_scan_alerts_table.sql index 308edbea..59bb1513 100644 --- a/backend/libs/persistence/src/main/resources/db/migration/V4__create_code_scan_alerts_table.sql +++ b/backend/libs/persistence/src/main/resources/db/migration/V4__create_code_scan_alerts_table.sql @@ -4,7 +4,7 @@ CREATE TABLE code_scan_alerts( severity VARCHAR(50) NOT NULL, file VARCHAR(255) NOT NULL, line INTEGER CHECK (line>=0), - branch VARCHAR(250) DEFAULT, + branch VARCHAR(250), status VARCHAR(50) NOT NULL , tool VARCHAR(255) NOT NULL, detected_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, From fced438dc427cfd0931d0376ddf6f567715758e1 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Tue, 30 Jun 2026 02:44:33 +0400 Subject: [PATCH 45/50] fix(scanning): delete verify() method from CodeScanAlertScannerTest to resolve test problem --- .../dev/cleat/scanning/CodeScanAlertScannerTest.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/backend/libs/scanning/src/test/java/dev/cleat/scanning/CodeScanAlertScannerTest.java b/backend/libs/scanning/src/test/java/dev/cleat/scanning/CodeScanAlertScannerTest.java index 2c72bdd8..bde870aa 100644 --- a/backend/libs/scanning/src/test/java/dev/cleat/scanning/CodeScanAlertScannerTest.java +++ b/backend/libs/scanning/src/test/java/dev/cleat/scanning/CodeScanAlertScannerTest.java @@ -1,17 +1,12 @@ package dev.cleat.scanning; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.verify; - import dev.cleat.common.enums.Severity; import dev.cleat.common.enums.Status; import dev.cleat.persistence.entity.CodeScanAlertEntity; -import dev.cleat.persistence.repository.CodeScanAlertRepository; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; -import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) @@ -20,9 +15,6 @@ public class CodeScanAlertScannerTest { @InjectMocks CodeScanAlertScanner codeScanAlertScanner; - @Mock - CodeScanAlertRepository codeScanAlertRepository; - @Test void whenProcessAlertThenStatusShouldBeOpen() { @@ -34,6 +26,5 @@ void whenProcessAlertThenStatusShouldBeOpen() { // then Assertions.assertEquals(Status.OPEN, codeScanAlertEntity.getStatus()); - verify(codeScanAlertRepository).save(any()); } } From f584d6c3b057434579f4721bfac3e9f1ea171895 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Tue, 30 Jun 2026 03:01:41 +0400 Subject: [PATCH 46/50] refactor(scanning): input validation moved out of try block to improve error handling --- .../dev/cleat/scanning/VulnerabilityScanner.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/backend/libs/scanning/src/main/java/dev/cleat/scanning/VulnerabilityScanner.java b/backend/libs/scanning/src/main/java/dev/cleat/scanning/VulnerabilityScanner.java index 66fe5cf5..63a2fa61 100644 --- a/backend/libs/scanning/src/main/java/dev/cleat/scanning/VulnerabilityScanner.java +++ b/backend/libs/scanning/src/main/java/dev/cleat/scanning/VulnerabilityScanner.java @@ -22,11 +22,15 @@ public VulnerabilityScanner( public void processAndSave(VulnerabilityEntity vulnerabilityEntity) { + if (vulnerabilityEntity == null) { + throw new IllegalArgumentException("VulnerabilityEntity cannot be null"); + } + if (vulnerabilityEntity.getCvss() == null + || vulnerabilityEntity.getSeverity() == null + || vulnerabilityEntity.getKev() == null) { + throw new IllegalArgumentException("cvss,severity and kev are required"); + } try { - if (vulnerabilityEntity == null) { - throw new IllegalArgumentException("VulnerabilityEntity cannot be null"); - } - Vulnerability vulnerability = new Vulnerability( vulnerabilityEntity.getKev(), vulnerabilityEntity.getCvss(), vulnerabilityEntity.getSeverity()); vulnerabilityEntity.setPriority(priorityCalculator.calculate(vulnerability)); From 091ddf17ee934edd9fb32fe2b845201256311786 Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Tue, 30 Jun 2026 03:14:22 +0400 Subject: [PATCH 47/50] test(scanning): update unit test to verify persistence calls via verify() --- .../cleat/scanning/VulnerabilityScannerTest.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java b/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java index e3735d0b..1e36e024 100644 --- a/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java +++ b/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java @@ -4,6 +4,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import dev.cleat.common.enums.Priority; @@ -11,7 +13,6 @@ import dev.cleat.domain.PriorityCalculator; import dev.cleat.persistence.entity.VulnerabilityEntity; import dev.cleat.persistence.repository.VulnerabilityRepository; -import java.util.Optional; import java.util.UUID; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -37,21 +38,16 @@ void givenVulnerabilityWithKevAndHighCvssWhenProcessingThenPriorityShouldBeUrgen VulnerabilityEntity vulnerabilityEntity = new VulnerabilityEntity().setId(uuid).setKev(true).setCvss(9.5).setSeverity(Severity.HIGH); - when(vulnerabilityRepository.findById(uuid)).thenReturn(Optional.of(vulnerabilityEntity)); - when(priorityCalculator.calculate(any())).thenReturn(Priority.URGENT); // when vulnerabilityScanner.processAndSave(vulnerabilityEntity); // then - VulnerabilityEntity savedVulnerabilityEntity = - vulnerabilityRepository.findById(uuid).get(); - assertNotNull(savedVulnerabilityEntity.getPriority(), "Priority should not be null"); + verify(vulnerabilityRepository, times(1)).save(vulnerabilityEntity); + assertNotNull(vulnerabilityEntity.getPriority(), "Priority should not be null"); assertEquals( - Priority.URGENT, - savedVulnerabilityEntity.getPriority(), - "Priority should be URGENT for high CVSS and KEV"); + Priority.URGENT, vulnerabilityEntity.getPriority(), "Priority should be URGENT for high CVSS and KEV"); } @Test From fbe86085916063131f7f2f029fc2c2c50b65605b Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Tue, 30 Jun 2026 03:47:36 +0400 Subject: [PATCH 48/50] test(scanning): refine unit test in VulnerabilityScannerTest --- .../java/dev/cleat/scanning/VulnerabilityScannerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java b/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java index 1e36e024..f090187e 100644 --- a/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java +++ b/backend/libs/scanning/src/test/java/dev/cleat/scanning/VulnerabilityScannerTest.java @@ -51,11 +51,11 @@ void givenVulnerabilityWithKevAndHighCvssWhenProcessingThenPriorityShouldBeUrgen } @Test - void whenCvssIsInvalidThenShouldThrowException() { + void whenCalculatorThrowsExceptionThenScannerWrapsItInRuntimeException() { // given VulnerabilityEntity vulnerabilityEntity = - new VulnerabilityEntity().setKev(true).setCvss(15.0).setSeverity(Severity.HIGH); + new VulnerabilityEntity().setKev(true).setCvss(9.0).setSeverity(Severity.HIGH); when(priorityCalculator.calculate(any())).thenThrow(new IllegalArgumentException("Invalid CVSS")); From bf112930f780ba7d314741d2a6e08fa319f8b80a Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Tue, 30 Jun 2026 04:12:43 +0400 Subject: [PATCH 49/50] feat(worker): implement comprehensive SecurityScanWorker for scheduled batch processing --- .../dev/cleat/worker/SecurityScanWorker.java | 78 +++++++++++++++++++ .../dev/cleat/worker/VulnerabilityWorker.java | 32 -------- .../cleat/scanning/CodeScanAlertScanner.java | 2 - 3 files changed, 78 insertions(+), 34 deletions(-) create mode 100644 backend/apps/worker/src/main/java/dev/cleat/worker/SecurityScanWorker.java delete mode 100644 backend/apps/worker/src/main/java/dev/cleat/worker/VulnerabilityWorker.java diff --git a/backend/apps/worker/src/main/java/dev/cleat/worker/SecurityScanWorker.java b/backend/apps/worker/src/main/java/dev/cleat/worker/SecurityScanWorker.java new file mode 100644 index 00000000..f045b618 --- /dev/null +++ b/backend/apps/worker/src/main/java/dev/cleat/worker/SecurityScanWorker.java @@ -0,0 +1,78 @@ +package dev.cleat.worker; + +import dev.cleat.persistence.repository.CodeScanAlertRepository; +import dev.cleat.persistence.repository.SecretFindingRepository; +import dev.cleat.persistence.repository.VulnerabilityRepository; +import dev.cleat.scanning.CodeScanAlertScanner; +import dev.cleat.scanning.SecretFindingScanner; +import dev.cleat.scanning.VulnerabilityScanner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Component +public class SecurityScanWorker { + private final VulnerabilityScanner vulnerabilityScanner; + private final VulnerabilityRepository vulnerabilityRepository; + private static final Logger LOG = LoggerFactory.getLogger(SecurityScanWorker.class); + private final SecretFindingRepository secretFindingRepository; + private final SecretFindingScanner secretFindingScanner; + private final CodeScanAlertRepository codeScanAlertRepository; + private final CodeScanAlertScanner codeScanAlertScanner; + + public SecurityScanWorker( + VulnerabilityScanner vulnerabilityScanner, + VulnerabilityRepository vulnerabilityRepository, + SecretFindingRepository secretFindingRepository, + SecretFindingScanner secretFindingScanner, + CodeScanAlertRepository codeScanAlertRepository, + CodeScanAlertScanner codeScanAlertScanner) { + this.vulnerabilityScanner = vulnerabilityScanner; + this.vulnerabilityRepository = vulnerabilityRepository; + this.secretFindingRepository = secretFindingRepository; + this.secretFindingScanner = secretFindingScanner; + this.codeScanAlertRepository = codeScanAlertRepository; + this.codeScanAlertScanner = codeScanAlertScanner; + } + + @Scheduled(fixedDelay = 600000) + public void runSecurityScan() { + LOG.info("Starting scheduled security scan..."); + processVulns(); + processSecrets(); + processAlerts(); + LOG.info("Scheduled security scan completed."); + } + + private void processVulns() { + vulnerabilityRepository.findAll().forEach(vulnerability -> { + try { + vulnerabilityScanner.processAndSave(vulnerability); + } catch (Exception ex) { + LOG.error("Failed to process vulnerability ID: {}.Skipping...", vulnerability.getId(), ex); + } + }); + } + + private void processSecrets() { + secretFindingRepository.findAll().forEach(secret -> { + try { + secretFindingScanner.process(secret); + } catch (Exception ex) { + LOG.error("Failed to process secret ID: {}", secret.getId(), ex); + } + }); + } + + private void processAlerts() { + codeScanAlertRepository.findAll().forEach(alert -> { + try { + codeScanAlertScanner.process(alert); + codeScanAlertRepository.save(alert); + } catch (Exception ex) { + LOG.error("Failed to process ID: {}", alert.getId(), ex); + } + }); + } +} diff --git a/backend/apps/worker/src/main/java/dev/cleat/worker/VulnerabilityWorker.java b/backend/apps/worker/src/main/java/dev/cleat/worker/VulnerabilityWorker.java deleted file mode 100644 index 8b3cc3f2..00000000 --- a/backend/apps/worker/src/main/java/dev/cleat/worker/VulnerabilityWorker.java +++ /dev/null @@ -1,32 +0,0 @@ -package dev.cleat.worker; - -import dev.cleat.persistence.repository.VulnerabilityRepository; -import dev.cleat.scanning.VulnerabilityScanner; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -@Component -public class VulnerabilityWorker { - private final VulnerabilityScanner vulnerabilityScanner; - private final VulnerabilityRepository vulnerabilityRepository; - private static final Logger LOG = LoggerFactory.getLogger(VulnerabilityWorker.class); - - public VulnerabilityWorker( - VulnerabilityScanner vulnerabilityScanner, VulnerabilityRepository vulnerabilityRepository) { - this.vulnerabilityScanner = vulnerabilityScanner; - this.vulnerabilityRepository = vulnerabilityRepository; - } - - @Scheduled(fixedDelay = 600000) - public void runSecurityScan() { - vulnerabilityRepository.findAll().forEach(vulnerability -> { - try { - vulnerabilityScanner.processAndSave(vulnerability); - } catch (Exception ex) { - LOG.error("Failed to process vulnerability ID: {}.Skipping...", vulnerability.getId(), ex); - } - }); - } -} diff --git a/backend/libs/scanning/src/main/java/dev/cleat/scanning/CodeScanAlertScanner.java b/backend/libs/scanning/src/main/java/dev/cleat/scanning/CodeScanAlertScanner.java index 0d452d06..5fc861ec 100644 --- a/backend/libs/scanning/src/main/java/dev/cleat/scanning/CodeScanAlertScanner.java +++ b/backend/libs/scanning/src/main/java/dev/cleat/scanning/CodeScanAlertScanner.java @@ -19,7 +19,5 @@ public void process(CodeScanAlertEntity codeScanAlertEntity) { } else { codeScanAlertEntity.setStatus(Status.OPEN); } - - LOG.info("Code scan alert processed successfully with status: {}", codeScanAlertEntity.getStatus()); } } From 530061ce84f6a0c0475e6f91792add8c42f7e29d Mon Sep 17 00:00:00 2001 From: arzunusretova12-debug Date: Tue, 30 Jun 2026 12:37:46 +0400 Subject: [PATCH 50/50] refactor(worker): move security scan fixedDelay to application.yml --- .../dev/cleat/api/exception/GlobalExceptionHandler.java | 4 ++-- .../src/main/java/dev/cleat/worker/SecurityScanWorker.java | 2 +- backend/apps/worker/src/main/resources/application.yml | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/backend/apps/api/src/main/java/dev/cleat/api/exception/GlobalExceptionHandler.java b/backend/apps/api/src/main/java/dev/cleat/api/exception/GlobalExceptionHandler.java index 1dc0dd36..6ce43e65 100644 --- a/backend/apps/api/src/main/java/dev/cleat/api/exception/GlobalExceptionHandler.java +++ b/backend/apps/api/src/main/java/dev/cleat/api/exception/GlobalExceptionHandler.java @@ -26,8 +26,8 @@ public ResponseEntity> handleGeneralException(Exception ex) if (ex instanceof ResponseStatusException) { throw (ResponseStatusException) ex; } - LOG.error("Unhandled exception occured: ", ex); + LOG.error("Unhandled exception occurred: ", ex); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(Map.of("error", "An unexpected error occured")); + .body(Map.of("error", "An unexpected error occurred")); } } diff --git a/backend/apps/worker/src/main/java/dev/cleat/worker/SecurityScanWorker.java b/backend/apps/worker/src/main/java/dev/cleat/worker/SecurityScanWorker.java index f045b618..d5feec6a 100644 --- a/backend/apps/worker/src/main/java/dev/cleat/worker/SecurityScanWorker.java +++ b/backend/apps/worker/src/main/java/dev/cleat/worker/SecurityScanWorker.java @@ -36,7 +36,7 @@ public SecurityScanWorker( this.codeScanAlertScanner = codeScanAlertScanner; } - @Scheduled(fixedDelay = 600000) + @Scheduled(fixedDelayString = "${app.security.scan-interval}") public void runSecurityScan() { LOG.info("Starting scheduled security scan..."); processVulns(); diff --git a/backend/apps/worker/src/main/resources/application.yml b/backend/apps/worker/src/main/resources/application.yml index ce6f2f6b..2fc5ab65 100644 --- a/backend/apps/worker/src/main/resources/application.yml +++ b/backend/apps/worker/src/main/resources/application.yml @@ -28,4 +28,8 @@ management: db: enabled: true redis: - enabled: true \ No newline at end of file + enabled: true + +app: + security: + scan-interval: 600000 \ No newline at end of file