From ee65b13228d2d75349e6e8f77c0ce49d46d30683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Risue=C3=B1o?= Date: Tue, 9 Jun 2026 11:36:05 +0200 Subject: [PATCH 1/6] Add created_at and updated_at logic --- .../services/ProjectComponentsService.java | 16 +++++++++++++ .../services/ProvisionerActionsService.java | 24 +++++++++++++++---- .../provisioner/ProjectComponent.java | 2 ++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opendevstack/component_catalog/server/services/ProjectComponentsService.java b/src/main/java/org/opendevstack/component_catalog/server/services/ProjectComponentsService.java index 0fd81cc..0aa97b9 100644 --- a/src/main/java/org/opendevstack/component_catalog/server/services/ProjectComponentsService.java +++ b/src/main/java/org/opendevstack/component_catalog/server/services/ProjectComponentsService.java @@ -34,6 +34,8 @@ public ProjectComponents addNewComponent(ProjectComponents projectComponents, String catalogItemId, Status status, String componentUrl, + String createdAt, + String updatedAt, List parameters) { var catalogItemIdWithoutBranch = getRepoPathFromCatalogItemId(catalogItemId); var branchReference = getBranchRefFromCatalogItemId(catalogItemId); @@ -48,6 +50,8 @@ public ProjectComponents addNewComponent(ProjectComponents projectComponents, .status(status) .catalogItemRef(branchReference) .componentUrl(componentUrl) + .createdAt(createdAt) + .updatedAt(updatedAt) .parameters(parameters) .build()); @@ -66,6 +70,8 @@ public ProjectComponents updateExistingComponent(ProjectComponents projectCompon String catalogItemId, Status status, String componentUrl, + String createdAt, + String updatedAt, List parameters) { Map components = projectComponents.getComponents(); @@ -88,6 +94,8 @@ public ProjectComponents updateExistingComponent(ProjectComponents projectCompon .status(status) .catalogItemRef(branchReference) .componentUrl(StringUtils.isBlank(componentUrl) ? existing.getComponentUrl() : componentUrl) + .createdAt(createdAt) + .updatedAt(updatedAt) .parameters(parameters) .build(); @@ -106,6 +114,8 @@ public ProjectComponents updatePartiallyExistingComponent(ProjectComponents proj Status status, String componentUrl, String workflowJobId, + String created_at, + String updated_at, List parameters) { validateComponentExists(projectComponents, componentId); @@ -122,6 +132,8 @@ public ProjectComponents updatePartiallyExistingComponent(ProjectComponents proj status, componentUrl, workflowJobId, + created_at, + updated_at, parameters) )); @@ -174,6 +186,8 @@ private ProjectComponent updateComponentIfMatch(Map.Entry parameters) { if (!entry.getKey().equals(componentId)) { @@ -187,6 +201,8 @@ private ProjectComponent updateComponentIfMatch(Map.Entry parameters; @Value("${catalog-item.reference.encoded}") From 4d2e29d78db7f0858422dc45edd03c9dd1387e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Risue=C3=B1o?= Date: Tue, 9 Jun 2026 12:01:18 +0200 Subject: [PATCH 2/6] Fix npe at updateComponentProvisioningStatus method --- .../server/services/ProvisionerActionsService.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsService.java b/src/main/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsService.java index 950e5e7..8d178a3 100644 --- a/src/main/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsService.java +++ b/src/main/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsService.java @@ -58,20 +58,19 @@ public void updateComponentProvisioningStatus(String projectKey, ProjectComponents updatedProjectComponents; var currentTimestamp = System.currentTimeMillis(); - var createdAt = projectComponents.getComponents().get(componentId).getCreatedAt(); - var updatedAt = String.valueOf(currentTimestamp); - projectComponents.getComponents().get(componentId).setUpdatedAt(updatedAt); if (existsComponent) { log.trace("Updating componentKey: {} to projectComponents: {}. Status: {}", componentId, projectComponents, status); + var createdAt = projectComponents.getComponents().get(componentId).getCreatedAt(); + var updatedAt = String.valueOf(currentTimestamp); updatedProjectComponents = projectComponentsService.updateExistingComponent( projectComponents, componentId, catalogItemId, status, componentUrl, createdAt, updatedAt, projectComponentParameters); } else { log.trace("Adding new componentKey: {} to projectComponents: {}", componentId, projectComponents); - createdAt = String.valueOf(currentTimestamp); - projectComponents.getComponents().get(componentId).setCreatedAt(createdAt); + var createdAt = String.valueOf(currentTimestamp); + var updatedAt = String.valueOf(currentTimestamp); updatedProjectComponents = projectComponentsService.addNewComponent( projectComponents, componentId, catalogItemId, status, componentUrl, createdAt, updatedAt, projectComponentParameters); } From f4eff360f4a7428629e2b2b5c87919aa56bec951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Risue=C3=B1o?= Date: Tue, 9 Jun 2026 12:17:44 +0200 Subject: [PATCH 3/6] Fix current tests && add new ones --- .../ProjectComponentsServiceTest.java | 62 +- .../ProvisionerActionsServiceTest.java | 1269 ++++++++++------- 2 files changed, 770 insertions(+), 561 deletions(-) diff --git a/src/test/java/org/opendevstack/component_catalog/server/services/ProjectComponentsServiceTest.java b/src/test/java/org/opendevstack/component_catalog/server/services/ProjectComponentsServiceTest.java index 266113b..0912492 100644 --- a/src/test/java/org/opendevstack/component_catalog/server/services/ProjectComponentsServiceTest.java +++ b/src/test/java/org/opendevstack/component_catalog/server/services/ProjectComponentsServiceTest.java @@ -43,7 +43,7 @@ void givenValidInput_whenAddNewComponent_thenComponentAdded() { String encoded = base64("repo/path?at=refs/heads/main"); //when - ProjectComponents updated = service.addNewComponent(pc, "comp1", encoded, Status.CREATING, "url", Collections.emptyList()); + ProjectComponents updated = service.addNewComponent(pc, "comp1", encoded, Status.CREATING, "url", "created", "updated", Collections.emptyList()); //then assertThat(updated.getComponents()).containsKey("comp1"); @@ -54,6 +54,8 @@ void givenValidInput_whenAddNewComponent_thenComponentAdded() { assertThat(added.getCatalogItemRef()).isEqualTo(base64("?at=refs/heads/main")); assertThat(added.getStatus()).isEqualTo(Status.CREATING); assertThat(added.getComponentUrl()).isEqualTo("url"); + assertThat(added.getCreatedAt()).isEqualTo("created"); + assertThat(added.getUpdatedAt()).isEqualTo("updated"); } @Test @@ -70,6 +72,8 @@ void givenExistingComponent_whenUpdateExistingComponent_thenUpdatedCorrectly() { .catalogItemRef(null) .componentUrl("oldUrl") .status(Status.CREATING) + .createdAt("oldCreated") + .updatedAt("oldUpdated") .build(); ProjectComponents pc = ProjectComponents.builder() @@ -78,7 +82,7 @@ void givenExistingComponent_whenUpdateExistingComponent_thenUpdatedCorrectly() { //when ProjectComponents updated = - service.updateExistingComponent(pc, "comp1", encodedFull, Status.CREATED, "newUrl", parameters); + service.updateExistingComponent(pc, "comp1", encodedFull, Status.CREATED, "newUrl", "created", "updated", parameters); //then ProjectComponent updatedComp = updated.getComponents().get("comp1"); @@ -87,6 +91,8 @@ void givenExistingComponent_whenUpdateExistingComponent_thenUpdatedCorrectly() { assertThat(updatedComp.getCatalogItemRef()).isEqualTo(base64("?at=refs/heads/main")); assertThat(updatedComp.getComponentUrl()).isEqualTo("newUrl"); assertThat(updatedComp.getParameters()).containsExactly(parameter); + assertThat(updatedComp.getCreatedAt()).isEqualTo("created"); + assertThat(updatedComp.getUpdatedAt()).isEqualTo("updated"); } @Test @@ -106,7 +112,7 @@ void givenDifferentRepoPath_whenUpdateExistingComponent_thenDoNotUpdateComponent //when ProjectComponents updated = - service.updateExistingComponent(pc, "comp1", encodedFullDifferent, Status.CREATED, "x", Collections.emptyList()); + service.updateExistingComponent(pc, "comp1", encodedFullDifferent, Status.CREATED, "x", "created", "updated", Collections.emptyList()); //then assertThat(updated.getComponents().get("comp1").getCatalogItemId()) @@ -122,7 +128,7 @@ void givenNonExistingComponent_whenUpdateExistingComponent_thenThrow() { //when //then assertThatThrownBy(() -> - service.updateExistingComponent(pc, "unknown", "zzz", Status.CREATED, "x", Collections.emptyList())) + service.updateExistingComponent(pc, "unknown", "zzz", Status.CREATED, "x", "created", "updated", Collections.emptyList())) .isInstanceOf(InvalidComponentStateException.class); } @@ -141,6 +147,8 @@ void givenExistingComponent_whenUpdatePartially_thenUpdatesOnlyFieldsProvided() .catalogItemRef(base64("?at=refs/heads/main")) .componentUrl("oldUrl") .status(Status.CREATING) + .createdAt("oldCreatedAt") + .updatedAt("oldUpdatedAt") .build(); ProjectComponents pc = ProjectComponents.builder() @@ -149,7 +157,7 @@ void givenExistingComponent_whenUpdatePartially_thenUpdatesOnlyFieldsProvided() //when ProjectComponents updated = - service.updatePartiallyExistingComponent(pc, "comp1", encodedFull, Status.CREATED, null, null, parameters); + service.updatePartiallyExistingComponent(pc, "comp1", encodedFull, Status.CREATED, null, null, "created", "updated", parameters); //then ProjectComponent result = updated.getComponents().get("comp1"); @@ -157,6 +165,8 @@ void givenExistingComponent_whenUpdatePartially_thenUpdatesOnlyFieldsProvided() assertThat(result.getStatus()).isEqualTo(Status.CREATED); assertThat(result.getComponentUrl()).isEqualTo("oldUrl"); // unchanged assertThat(result.getCatalogItemRef()).isEqualTo(base64("?at=refs/heads/dev")); + assertThat(result.getCreatedAt()).isEqualTo("created"); + assertThat(result.getUpdatedAt()).isEqualTo("updated"); } @Test @@ -168,7 +178,7 @@ void givenNonExistingComponent_whenUpdatePartially_thenThrow() { //when //then assertThatThrownBy(() -> - service.updatePartiallyExistingComponent(pc, "missing", "zzz", Status.CREATED, "x", null, Collections.emptyList())) + service.updatePartiallyExistingComponent(pc, "missing", "zzz", Status.CREATED, "x", null, "created", "updated", Collections.emptyList())) .isInstanceOf(InvalidComponentStateException.class); } @@ -204,7 +214,7 @@ void givenBlankWorkflowJobId_whenUpdatePartially_thenKeepsExistingWorkflowJobId( //when ProjectComponents updated = - service.updatePartiallyExistingComponent(pc, "comp1", null, Status.CREATED, null, "", Collections.emptyList()); + service.updatePartiallyExistingComponent(pc, "comp1", null, Status.CREATED, null, "", null, null, Collections.emptyList()); //then assertThat(updated.getComponents().get("comp1").getWorkflowJobId()).isEqualTo("existing-job-id"); @@ -227,7 +237,7 @@ void givenNewWorkflowJobId_whenUpdatePartially_thenUsesNewWorkflowJobId() { //when ProjectComponents updated = - service.updatePartiallyExistingComponent(pc, "comp1", null, Status.CREATED, null, "new-job-id", Collections.emptyList()); + service.updatePartiallyExistingComponent(pc, "comp1", null, Status.CREATED, null, "new-job-id", null, null, Collections.emptyList()); //then assertThat(updated.getComponents().get("comp1").getWorkflowJobId()).isEqualTo("new-job-id"); @@ -257,4 +267,40 @@ void givenValidCatalogItemId_whenExtractBranchRef_thenReturnEncodedBranch() { assertThat(repoPath).isEqualTo(base64("repo/x")); } + @Test + void givenNullTimestamps_whenUpdatePartially_thenTimestampsAreOverwrittenWithNull() { + //given + String encodedRepo = base64("repo/a"); + + ProjectComponent existing = ProjectComponent.builder() + .componentId("comp1") + .catalogItemId(encodedRepo) + .createdAt("oldCreated") + .updatedAt("oldUpdated") + .status(Status.CREATING) + .build(); + + ProjectComponents pc = ProjectComponents.builder() + .components(Map.of("comp1", existing)) + .build(); + + //when + ProjectComponents updated = service.updatePartiallyExistingComponent( + pc, + "comp1", + null, + Status.CREATED, + null, + null, + null, + null, + Collections.emptyList()); + + //then + ProjectComponent result = updated.getComponents().get("comp1"); + + assertThat(result.getCreatedAt()).isNull(); + assertThat(result.getUpdatedAt()).isNull(); + } + } \ No newline at end of file diff --git a/src/test/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsServiceTest.java b/src/test/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsServiceTest.java index b498533..a744755 100644 --- a/src/test/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsServiceTest.java +++ b/src/test/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsServiceTest.java @@ -1,553 +1,716 @@ -package org.opendevstack.component_catalog.server.services; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectWriter; -import org.apache.commons.lang3.tuple.Pair; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.opendevstack.component_catalog.config.ProvisionerActionsConfiguration; -import org.opendevstack.component_catalog.server.controllers.exceptions.RestEntityNotFoundException; -import org.opendevstack.component_catalog.server.mother.BitbucketPathAtMother; -import org.opendevstack.component_catalog.server.mother.ProjectComponentsMother; -import org.opendevstack.component_catalog.server.services.bitbucket.BitbucketPathAt; -import org.opendevstack.component_catalog.server.services.exceptions.ComponentAlreadyExistsException; -import org.opendevstack.component_catalog.server.services.exceptions.ElementNotFoundException; -import org.opendevstack.component_catalog.server.services.exceptions.InvalidEntityException; -import org.opendevstack.component_catalog.server.services.provisioner.Parameter; -import org.opendevstack.component_catalog.server.services.provisioner.ProjectComponent; -import org.opendevstack.component_catalog.server.services.provisioner.ProjectComponents; -import org.opendevstack.component_catalog.server.services.provisioner.Status; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.web.client.HttpClientErrorException; - -import java.util.Base64; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class ProvisionerActionsServiceTest { - - @Mock - private BitbucketService bitbucketService; - - @Mock - private ProjectComponentsService projectComponentsService; - - @Mock - private ObjectMapper objectMapper; - - @Mock - private ObjectWriter objectWriter; - - @Mock - private BitbucketPathAt.BitbucketPathAtBuilder bitbucketPathAtBuilder; - - private ProvisionerActionsConfiguration provisionerActionsConfiguration; - - private ProvisionerActionsService provisionerActionsService; - - @BeforeEach - void setUp() { - provisionerActionsConfiguration = new ProvisionerActionsConfiguration(); - populateProvisionerActionsConfiguration(); - - provisionerActionsService = new ProvisionerActionsService(bitbucketService, objectMapper, projectComponentsService, provisionerActionsConfiguration); - } - - @Test - void givenAProvisionersObject_andCreatingStatus_whenValidate_andComponentIdExists_thenThrowException() { - // given - var componentId = "componentId"; - var status = Status.CREATING; - - ProjectComponents projectComponents = ProjectComponentsMother.of(); - - - // when - var exception = assertThrows(ComponentAlreadyExistsException.class, () -> - provisionerActionsService.validate(projectComponents, componentId, status) - ); - - // then - assertThat(exception.getMessage()).isEqualTo("Component with id 'componentId' already exists in the project components."); - } - - @Test - void givenAProvisionersObject_andCreatingStatus_whenUpdateComponentProvisioningStatus_thenBitbucketFileIsUpdated() throws JsonProcessingException { - // given - var projectKey = "projectKey"; - var status = Status.CREATING; - var componentId = "componentId"; - var catalogItemId = "catalogItemId"; - var componentUrl = "catalogUrl"; - - var pathAt = BitbucketPathAtMother.of(); - var sourceCommitId = "sourceCommitId"; - - var projectComponents = new ProjectComponents(); - var updatedProjectComponents = ProjectComponentsMother.of(); - - var parameterParam = Pair.of("parameterName", List.of("parameterValue")); - - var parameter = Parameter.builder().name(parameterParam.getLeft()).values(parameterParam.getValue()).build(); - var parameters = List.of(parameter); - - prepareMocksForGetBitbucketPathAt(pathAt); - when(bitbucketService.getLastCommit(pathAt)).thenReturn(Optional.of(sourceCommitId)); - - prepareMocksForGetNonExistingProjectComponents(pathAt, projectComponents); - when(projectComponentsService.addNewComponent( - projectComponents, - componentId, - catalogItemId, - status, - componentUrl, - parameters - )).thenReturn(updatedProjectComponents); - - var serializedUpdatedProjectComponents = prepareMocksForSave(updatedProjectComponents); - - // when - provisionerActionsService.updateComponentProvisioningStatus( - projectKey, - status, - componentId, - catalogItemId, - componentUrl, - List.of(parameterParam) - ); - - // then - verify(bitbucketService).pushFile( - pathAt, - sourceCommitId, - serializedUpdatedProjectComponents - ); - } - - @ParameterizedTest - @EnumSource(value = Status.class, names = { "CREATED", "FAILED", "DELETING", "UNKNOWN" }) - void givenAProvisionersObject_andSelectedStatuses_whenUpdateComponentProvisioningStatus_thenBitbucketFileIsUpdated( - Status status - ) throws JsonProcessingException { - // given - var projectKey = "projectKey"; - var componentId = "componentId"; - var catalogItemId = "catalogItemId"; - var componentUrl = "catalogUrl"; - - var pathAt = BitbucketPathAtMother.of(); - var sourceCommitId = "sourceCommitId"; - - var projectComponents = ProjectComponentsMother.of(); - var updatedProjectComponents = ProjectComponentsMother.of(); - - prepareMocksForGetBitbucketPathAt(pathAt); - when(bitbucketService.getLastCommit(pathAt)).thenReturn(Optional.of(sourceCommitId)); - - prepareMocksForGetExistingProjectComponents(pathAt, projectComponents); - when(projectComponentsService.updateExistingComponent( - projectComponents, - componentId, - catalogItemId, - status, - componentUrl, - Collections.emptyList() - )).thenReturn(updatedProjectComponents); - - var serializedUpdatedProjectComponents = prepareMocksForSave(updatedProjectComponents); - - // when - provisionerActionsService.updateComponentProvisioningStatus( - projectKey, - status, - componentId, - catalogItemId, - componentUrl, - Collections.emptyList() - ); - - // then - verify(bitbucketService).pushFile( - pathAt, - sourceCommitId, - serializedUpdatedProjectComponents - ); - } - - @Test - void givenASetOfProjectComponents_andACatalogItemId_andCatalogItemIdInComponents_whenIsProvisioned_thenReturnTrue() throws InvalidEntityException { - // given - var rawId = "my-item"; - var rawIdWithBranch = "my-item?at=refs/heads/main"; - - var encodedId = Base64.getEncoder().encodeToString(rawId.getBytes()); - var encodedIdWithBranch = Base64.getEncoder().encodeToString(rawIdWithBranch.getBytes()); - - var matchingComponent = new ProjectComponent(); - matchingComponent.setCatalogItemId(encodedId); - - var dummy1 = new ProjectComponent(); - dummy1.setCatalogItemId(Base64.getEncoder().encodeToString("dummy-1".getBytes())); - - var dummy2 = new ProjectComponent(); - dummy2.setCatalogItemId(Base64.getEncoder().encodeToString("dummy-2".getBytes())); - - var components = new HashMap(); - components.put("c1", dummy1); - components.put("c2", matchingComponent); // only this one matches - components.put("c3", dummy2); - - var projectComponents = new ProjectComponents(); - projectComponents.setComponents(components); - - when(projectComponentsService.getRepoPathFromCatalogItemId(anyString())).thenCallRealMethod(); - - // when - var result = provisionerActionsService.isProvisioned(projectComponents, encodedIdWithBranch); - - // then - assertThat(result).isTrue(); - } - - @Test - void givenASetOfProjectComponents_andACatalogItemId_andCatalogItemIdNotInComponents_whenIsProvisioned_thenReturnFalse() throws InvalidEntityException { - // given - var rawId = "my-item"; - var encodedId = Base64.getEncoder().encodeToString(rawId.getBytes()); - - var dummy1 = new ProjectComponent(); - dummy1.setCatalogItemId(Base64.getEncoder().encodeToString("dummy-1".getBytes())); - - var dummy2 = new ProjectComponent(); - dummy2.setCatalogItemId(Base64.getEncoder().encodeToString("dummy-2".getBytes())); - - var components = new HashMap(); - components.put("c1", dummy1); - components.put("c2", dummy2); - - var projectComponents = new ProjectComponents(); - projectComponents.setComponents(components); - - when(projectComponentsService.getRepoPathFromCatalogItemId(anyString())).thenCallRealMethod(); - - // when - var result = provisionerActionsService.isProvisioned(projectComponents, encodedId); - - // then - assertThat(result).isFalse(); - } - - @Test - void givenAProjectKey_andAComponentId_whenDeleteComponentProvisioningStatus_thenBitbucketServiceIsCalled() throws JsonProcessingException { - // given - var projectKey = "projectKey"; - var componentId = "componentId"; - - var pathAtBuilder = mock(BitbucketPathAt.BitbucketPathAtBuilder.class); - var pathAt = mock(BitbucketPathAt.class); - var sourceCommitId = "sourceCommitId"; - - var projectComponents = ProjectComponentsMother.of(); - var projectComponentsWithoutComponentId = ProjectComponentsMother.of(); - - when(bitbucketService.pathAtBuilder()).thenReturn(pathAtBuilder); - when(pathAtBuilder.projectKey(any())).thenReturn(pathAtBuilder); - when(pathAtBuilder.repoSlug(any())).thenReturn(pathAtBuilder); - when(pathAtBuilder.subPath(any())).thenReturn(pathAtBuilder); - when(pathAtBuilder.at(any())).thenReturn(pathAtBuilder); - when(pathAtBuilder.build()).thenReturn(pathAt); - - when(bitbucketService.getLastCommit(pathAt)).thenReturn(Optional.of(sourceCommitId)); - when(projectComponentsService.deleteComponent(projectComponents, componentId)).thenReturn(projectComponentsWithoutComponentId); - - prepareMocksForGetExistingProjectComponents(pathAt, projectComponents); - var serializedProjectComponentsWithoutComponentId = prepareMocksForSave(projectComponentsWithoutComponentId); - - // when - provisionerActionsService.deleteComponentProvisioningStatus(projectKey, componentId); - - // then - verify(bitbucketService).pushFile(pathAt, sourceCommitId, serializedProjectComponentsWithoutComponentId); - } - - @Test - void givenAProjectKey_andAComponentId_whenDeleteComponentProvisioningStatus_andNoProjectComponentsForProjectKey_thenExceptionIsThrown() { - // given - var projectKey = "projectKey"; - var componentId = "componentId"; - - var pathAtBuilder = mock(BitbucketPathAt.BitbucketPathAtBuilder.class); - var pathAt = mock(BitbucketPathAt.class); - - var projectComponents = ProjectComponentsMother.of(); - - when(bitbucketService.pathAtBuilder()).thenReturn(pathAtBuilder); - when(pathAtBuilder.projectKey(any())).thenReturn(pathAtBuilder); - when(pathAtBuilder.repoSlug(any())).thenReturn(pathAtBuilder); - when(pathAtBuilder.subPath(any())).thenReturn(pathAtBuilder); - when(pathAtBuilder.at(any())).thenReturn(pathAtBuilder); - when(pathAtBuilder.build()).thenReturn(pathAt); - - when(bitbucketService.getLastCommit(pathAt)).thenReturn(Optional.empty()); - - assertThat(projectComponents.getComponents()).containsKey(componentId); - - // when - var exception = assertThrows(RestEntityNotFoundException.class, () -> - provisionerActionsService.deleteComponentProvisioningStatus(projectKey, componentId) - ); - - // then - assertThat(exception.getMessage()).startsWith("No component provisioning status for pathAt:"); - verify(bitbucketService, times(0)).pushFile(any(), any(), anyString()); - } - - @Test - void givenAProjectComponents_whenSaveProjectComponents_andBitbucketRejectAsNoUpdates_ThenExceptionIsIgnored() throws JsonProcessingException { - // given - var pathAt = mock(BitbucketPathAt.class); - var sourceCommitId = "sourceCommitId"; - var updatedProjectComponents = ProjectComponentsMother.of(); - var httpClientErrorException = new HttpClientErrorException(HttpStatus.CONFLICT, "\"{\"errors\":" + - "[{\"context\":null,\"message\":\"The content provided is the same as what already exists. No change was committed.\"" + - ",\"exceptionName\":\"com.atlassian.bitbucket.content.FileContentUnmodifiedException\"}]}\""); - - doThrow(httpClientErrorException).when(bitbucketService).pushFile(eq(pathAt), eq(sourceCommitId), anyString()); - prepareMocksForSave(updatedProjectComponents); - - // when - provisionerActionsService.saveProjectComponents(pathAt, sourceCommitId, updatedProjectComponents); - - // then - // no exception is thrown - } - - @Test - void givenAProjectComponents_whenSaveProjectComponents_andBitbucketReject_ThenExceptionIsThrown() throws JsonProcessingException { - // given - var pathAt = mock(BitbucketPathAt.class); - var sourceCommitId = "sourceCommitId"; - var updatedProjectComponents = ProjectComponentsMother.of(); - var httpClientErrorException = new HttpClientErrorException(HttpStatus.CONFLICT, "Client Error"); - - doThrow(httpClientErrorException).when(bitbucketService).pushFile(eq(pathAt), eq(sourceCommitId), anyString()); - prepareMocksForSave(updatedProjectComponents); - - // when - var exception = assertThrows(HttpClientErrorException.class, () -> - provisionerActionsService.saveProjectComponents(pathAt, sourceCommitId, updatedProjectComponents) - ); - - // then - assertThat(exception).isEqualTo(httpClientErrorException); - } - - @Test - void givenExistingProjectComponents_whenUpdatePartiallyComponentProvisioningStatus_thenBitbucketFileIsUpdated() - throws JsonProcessingException { - - // given - var projectKey = "projectKey"; - var status = Status.FAILED; // any status is allowed - var componentId = "componentId"; - var catalogItemId = "catalogItemId"; - var componentUrl = "componentUrl"; - var parameterPair = Pair.of("paramName", List.of("paramValue")); - var workflowJobId = "workflowJobId"; - - var pathAt = BitbucketPathAtMother.of(); - var sourceCommitId = "sourceCommitId"; - - var projectComponents = ProjectComponentsMother.of(); - var updatedProjectComponents = ProjectComponentsMother.of(); - - // get path - prepareMocksForGetBitbucketPathAt(pathAt); - - // last commit exists - when(bitbucketService.getLastCommit(pathAt)).thenReturn(Optional.of(sourceCommitId)); - - // project components exist - prepareMocksForGetExistingProjectComponents(pathAt, projectComponents); - - // partial update call - when(projectComponentsService.updatePartiallyExistingComponent( - projectComponents, - componentId, - catalogItemId, - status, - componentUrl, - workflowJobId, - List.of(Parameter.builder().name("paramName").values(List.of("paramValue")).build()) - )).thenReturn(updatedProjectComponents); - - var serializedUpdatedProjectComponents = prepareMocksForSave(updatedProjectComponents); - - // when - provisionerActionsService.updatePartiallyComponentProvisioningStatus( - projectKey, - status, - componentId, - catalogItemId, - componentUrl, - workflowJobId, - List.of(parameterPair) - ); - - // then - verify(bitbucketService).pushFile( - pathAt, - sourceCommitId, - serializedUpdatedProjectComponents - ); - } - - @Test - void givenNoProjectComponents_whenUpdatePartiallyComponentProvisioningStatus_thenThrowException(){ - - // given - var projectKey = "projectKey"; - var status = Status.FAILED; - var componentId = "componentId"; - var catalogItemId = "catalogItemId"; - var componentUrl = "componentUrl"; - var workflowJobId = "workflowJobId"; - - var pathAt = BitbucketPathAtMother.of(); - - prepareMocksForGetBitbucketPathAt(pathAt); - - // Simulate null projectComponents - when(bitbucketService.getTextFileContents(pathAt)).thenReturn(Optional.empty()); - when(projectComponentsService.createNewComponent()).thenReturn(null); - - // when - var exception = assertThrows(ElementNotFoundException.class, () -> - provisionerActionsService.updatePartiallyComponentProvisioningStatus( - projectKey, - status, - componentId, - catalogItemId, - componentUrl, - workflowJobId, - List.of() - ) - ); - - // then - assertThat(exception.getMessage()) - .isEqualTo("In a partial update, the projectComponent should exist."); - } - - @Test - void givenNewFile_whenUpdatePartiallyComponentProvisioningStatus_thenPushFileIsCalledWithNullCommit() - throws JsonProcessingException { - - // given - var projectKey = "projectKey"; - var status = Status.UNKNOWN; - var componentId = "componentId"; - var catalogItemId = "catalogItemId"; - var componentUrl = "url"; - var workflowJobId = "workflowJobId"; - - var pathAt = BitbucketPathAtMother.of(); - - var projectComponents = ProjectComponentsMother.of(); - var updatedProjectComponents = ProjectComponentsMother.of(); - - prepareMocksForGetBitbucketPathAt(pathAt); - - // no last commit - when(bitbucketService.getLastCommit(pathAt)).thenReturn(Optional.empty()); - - // components exist - prepareMocksForGetExistingProjectComponents(pathAt, projectComponents); - - when(projectComponentsService.updatePartiallyExistingComponent( - eq(projectComponents), - eq(componentId), - eq(catalogItemId), - eq(status), - eq(componentUrl), - eq(workflowJobId), - any() - )).thenReturn(updatedProjectComponents); - - var serialized = prepareMocksForSave(updatedProjectComponents); - - // when - provisionerActionsService.updatePartiallyComponentProvisioningStatus( - projectKey, - status, - componentId, - catalogItemId, - componentUrl, - workflowJobId, - List.of() - ); - - // then - verify(bitbucketService).pushFile(pathAt, null, serialized); - } - - private String prepareMocksForSave(ProjectComponents updatedProjectComponents) throws JsonProcessingException { - var serializedUpdatedProjectComponents = "{ serialized-updated-json: true }"; - - when(objectMapper.writerWithDefaultPrettyPrinter()).thenReturn(objectWriter); - when(objectWriter.writeValueAsString(updatedProjectComponents)).thenReturn(serializedUpdatedProjectComponents); - - return serializedUpdatedProjectComponents; - } - - private void prepareMocksForGetNonExistingProjectComponents(BitbucketPathAt bitbucketPathAt, ProjectComponents projectComponents){ - when(bitbucketService.getTextFileContents(bitbucketPathAt)).thenReturn(Optional.empty()); - when(projectComponentsService.createNewComponent()).thenReturn(projectComponents); - } - - private void prepareMocksForGetExistingProjectComponents(BitbucketPathAt bitbucketPathAt, ProjectComponents projectComponents) throws JsonProcessingException { - var serializedProjectComponents = "{ serialized-json: true }"; - var bitbucketFileContent = Pair.of(MediaType.APPLICATION_JSON, serializedProjectComponents); - - when(bitbucketService.getTextFileContents(bitbucketPathAt)).thenReturn(Optional.of(bitbucketFileContent)); - when(objectMapper.readValue(serializedProjectComponents, ProjectComponents.class)).thenReturn(projectComponents); - } - - private void populateProvisionerActionsConfiguration() { - var projectKey = "configuredProjectKey"; - var repoSlug = "repoSlug"; - var subPath = "subPath"; - var subPathToken = "subPathToken"; - var branchName = "branchName"; - - provisionerActionsConfiguration.setProjectKey(projectKey); - provisionerActionsConfiguration.setRepositorySlug(repoSlug); - provisionerActionsConfiguration.setSubPath(subPath); - provisionerActionsConfiguration.setSubPathToken(subPathToken); - provisionerActionsConfiguration.setBranchName(branchName); - } - - private void prepareMocksForGetBitbucketPathAt(BitbucketPathAt bitbucketPathAt) { - when(bitbucketService.pathAtBuilder()).thenReturn(bitbucketPathAtBuilder); - when(bitbucketPathAtBuilder.projectKey(provisionerActionsConfiguration.getProjectKey())).thenReturn(bitbucketPathAtBuilder); - when(bitbucketPathAtBuilder.repoSlug(provisionerActionsConfiguration.getRepositorySlug())).thenReturn(bitbucketPathAtBuilder); - when(bitbucketPathAtBuilder.subPath(provisionerActionsConfiguration.getSubPath())).thenReturn(bitbucketPathAtBuilder); - when(bitbucketPathAtBuilder.at(provisionerActionsConfiguration.getBranchName())).thenReturn(bitbucketPathAtBuilder); - when(bitbucketPathAtBuilder.build()).thenReturn(bitbucketPathAt); - } -} +package org.opendevstack.component_catalog.server.services; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import lombok.val; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.opendevstack.component_catalog.config.ProvisionerActionsConfiguration; +import org.opendevstack.component_catalog.server.controllers.exceptions.RestEntityNotFoundException; +import org.opendevstack.component_catalog.server.mother.BitbucketPathAtMother; +import org.opendevstack.component_catalog.server.mother.ProjectComponentsMother; +import org.opendevstack.component_catalog.server.services.bitbucket.BitbucketPathAt; +import org.opendevstack.component_catalog.server.services.exceptions.ComponentAlreadyExistsException; +import org.opendevstack.component_catalog.server.services.exceptions.ElementNotFoundException; +import org.opendevstack.component_catalog.server.services.exceptions.InvalidEntityException; +import org.opendevstack.component_catalog.server.services.provisioner.Parameter; +import org.opendevstack.component_catalog.server.services.provisioner.ProjectComponent; +import org.opendevstack.component_catalog.server.services.provisioner.ProjectComponents; +import org.opendevstack.component_catalog.server.services.provisioner.Status; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.client.HttpClientErrorException; + +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class ProvisionerActionsServiceTest { + + @Mock + private BitbucketService bitbucketService; + + @Mock + private ProjectComponentsService projectComponentsService; + + @Mock + private ObjectMapper objectMapper; + + @Mock + private ObjectWriter objectWriter; + + @Mock + private BitbucketPathAt.BitbucketPathAtBuilder bitbucketPathAtBuilder; + + private ProvisionerActionsConfiguration provisionerActionsConfiguration; + + private ProvisionerActionsService provisionerActionsService; + + @BeforeEach + void setUp() { + provisionerActionsConfiguration = new ProvisionerActionsConfiguration(); + populateProvisionerActionsConfiguration(); + + provisionerActionsService = new ProvisionerActionsService(bitbucketService, objectMapper, projectComponentsService, provisionerActionsConfiguration); + } + + @Test + void givenAProvisionersObject_andCreatingStatus_whenValidate_andComponentIdExists_thenThrowException() { + // given + var componentId = "componentId"; + var status = Status.CREATING; + + ProjectComponents projectComponents = ProjectComponentsMother.of(); + + + // when + var exception = assertThrows(ComponentAlreadyExistsException.class, () -> + provisionerActionsService.validate(projectComponents, componentId, status) + ); + + // then + assertThat(exception.getMessage()).isEqualTo("Component with id 'componentId' already exists in the project components."); + } + + @Test + void givenAProvisionersObject_andCreatingStatus_whenUpdateComponentProvisioningStatus_thenBitbucketFileIsUpdated() throws JsonProcessingException { + // given + var projectKey = "projectKey"; + var status = Status.CREATING; + var componentId = "componentId"; + var catalogItemId = "catalogItemId"; + var componentUrl = "catalogUrl"; + + var pathAt = BitbucketPathAtMother.of(); + var sourceCommitId = "sourceCommitId"; + + var projectComponents = new ProjectComponents(); + var updatedProjectComponents = ProjectComponentsMother.of(); + + var parameterParam = Pair.of("parameterName", List.of("parameterValue")); + + var parameter = Parameter.builder().name(parameterParam.getLeft()).values(parameterParam.getValue()).build(); + var parameters = List.of(parameter); + + prepareMocksForGetBitbucketPathAt(pathAt); + when(bitbucketService.getLastCommit(pathAt)).thenReturn(Optional.of(sourceCommitId)); + + prepareMocksForGetNonExistingProjectComponents(pathAt, projectComponents); + when(projectComponentsService.addNewComponent( + eq(projectComponents), + eq(componentId), + eq(catalogItemId), + eq(status), + eq(componentUrl), + any(), + any(), + eq(parameters) + )).thenReturn(updatedProjectComponents); + + var serializedUpdatedProjectComponents = prepareMocksForSave(updatedProjectComponents); + + // when + provisionerActionsService.updateComponentProvisioningStatus( + projectKey, + status, + componentId, + catalogItemId, + componentUrl, + List.of(parameterParam) + ); + + // then + verify(bitbucketService).pushFile( + pathAt, + sourceCommitId, + serializedUpdatedProjectComponents + ); + } + + @ParameterizedTest + @EnumSource(value = Status.class, names = { "CREATED", "FAILED", "DELETING", "UNKNOWN" }) + void givenAProvisionersObject_andSelectedStatuses_whenUpdateComponentProvisioningStatus_thenBitbucketFileIsUpdated( + Status status + ) throws JsonProcessingException { + // given + var projectKey = "projectKey"; + var componentId = "componentId"; + var catalogItemId = "catalogItemId"; + var componentUrl = "catalogUrl"; + + var pathAt = BitbucketPathAtMother.of(); + var sourceCommitId = "sourceCommitId"; + + var projectComponents = ProjectComponentsMother.of(); + var updatedProjectComponents = ProjectComponentsMother.of(); + + prepareMocksForGetBitbucketPathAt(pathAt); + when(bitbucketService.getLastCommit(pathAt)).thenReturn(Optional.of(sourceCommitId)); + + prepareMocksForGetExistingProjectComponents(pathAt, projectComponents); + when(projectComponentsService.updateExistingComponent( + eq(projectComponents), + eq(componentId), + eq(catalogItemId), + eq(status), + eq(componentUrl), + any(), + any(), + eq(Collections.emptyList()) + )).thenReturn(updatedProjectComponents); + + var serializedUpdatedProjectComponents = prepareMocksForSave(updatedProjectComponents); + + // when + provisionerActionsService.updateComponentProvisioningStatus( + projectKey, + status, + componentId, + catalogItemId, + componentUrl, + Collections.emptyList() + ); + + // then + verify(bitbucketService).pushFile( + pathAt, + sourceCommitId, + serializedUpdatedProjectComponents + ); + } + + @Test + void givenASetOfProjectComponents_andACatalogItemId_andCatalogItemIdInComponents_whenIsProvisioned_thenReturnTrue() throws InvalidEntityException { + // given + var rawId = "my-item"; + var rawIdWithBranch = "my-item?at=refs/heads/main"; + + var encodedId = Base64.getEncoder().encodeToString(rawId.getBytes()); + var encodedIdWithBranch = Base64.getEncoder().encodeToString(rawIdWithBranch.getBytes()); + + var matchingComponent = new ProjectComponent(); + matchingComponent.setCatalogItemId(encodedId); + + var dummy1 = new ProjectComponent(); + dummy1.setCatalogItemId(Base64.getEncoder().encodeToString("dummy-1".getBytes())); + + var dummy2 = new ProjectComponent(); + dummy2.setCatalogItemId(Base64.getEncoder().encodeToString("dummy-2".getBytes())); + + var components = new HashMap(); + components.put("c1", dummy1); + components.put("c2", matchingComponent); // only this one matches + components.put("c3", dummy2); + + var projectComponents = new ProjectComponents(); + projectComponents.setComponents(components); + + when(projectComponentsService.getRepoPathFromCatalogItemId(anyString())).thenCallRealMethod(); + + // when + var result = provisionerActionsService.isProvisioned(projectComponents, encodedIdWithBranch); + + // then + assertThat(result).isTrue(); + } + + @Test + void givenASetOfProjectComponents_andACatalogItemId_andCatalogItemIdNotInComponents_whenIsProvisioned_thenReturnFalse() throws InvalidEntityException { + // given + var rawId = "my-item"; + var encodedId = Base64.getEncoder().encodeToString(rawId.getBytes()); + + var dummy1 = new ProjectComponent(); + dummy1.setCatalogItemId(Base64.getEncoder().encodeToString("dummy-1".getBytes())); + + var dummy2 = new ProjectComponent(); + dummy2.setCatalogItemId(Base64.getEncoder().encodeToString("dummy-2".getBytes())); + + var components = new HashMap(); + components.put("c1", dummy1); + components.put("c2", dummy2); + + var projectComponents = new ProjectComponents(); + projectComponents.setComponents(components); + + when(projectComponentsService.getRepoPathFromCatalogItemId(anyString())).thenCallRealMethod(); + + // when + var result = provisionerActionsService.isProvisioned(projectComponents, encodedId); + + // then + assertThat(result).isFalse(); + } + + @Test + void givenAProjectKey_andAComponentId_whenDeleteComponentProvisioningStatus_thenBitbucketServiceIsCalled() throws JsonProcessingException { + // given + var projectKey = "projectKey"; + var componentId = "componentId"; + + var pathAtBuilder = mock(BitbucketPathAt.BitbucketPathAtBuilder.class); + var pathAt = mock(BitbucketPathAt.class); + var sourceCommitId = "sourceCommitId"; + + var projectComponents = ProjectComponentsMother.of(); + var projectComponentsWithoutComponentId = ProjectComponentsMother.of(); + + when(bitbucketService.pathAtBuilder()).thenReturn(pathAtBuilder); + when(pathAtBuilder.projectKey(any())).thenReturn(pathAtBuilder); + when(pathAtBuilder.repoSlug(any())).thenReturn(pathAtBuilder); + when(pathAtBuilder.subPath(any())).thenReturn(pathAtBuilder); + when(pathAtBuilder.at(any())).thenReturn(pathAtBuilder); + when(pathAtBuilder.build()).thenReturn(pathAt); + + when(bitbucketService.getLastCommit(pathAt)).thenReturn(Optional.of(sourceCommitId)); + when(projectComponentsService.deleteComponent(projectComponents, componentId)).thenReturn(projectComponentsWithoutComponentId); + + prepareMocksForGetExistingProjectComponents(pathAt, projectComponents); + var serializedProjectComponentsWithoutComponentId = prepareMocksForSave(projectComponentsWithoutComponentId); + + // when + provisionerActionsService.deleteComponentProvisioningStatus(projectKey, componentId); + + // then + verify(bitbucketService).pushFile(pathAt, sourceCommitId, serializedProjectComponentsWithoutComponentId); + } + + @Test + void givenAProjectKey_andAComponentId_whenDeleteComponentProvisioningStatus_andNoProjectComponentsForProjectKey_thenExceptionIsThrown() { + // given + var projectKey = "projectKey"; + var componentId = "componentId"; + + var pathAtBuilder = mock(BitbucketPathAt.BitbucketPathAtBuilder.class); + var pathAt = mock(BitbucketPathAt.class); + + var projectComponents = ProjectComponentsMother.of(); + + when(bitbucketService.pathAtBuilder()).thenReturn(pathAtBuilder); + when(pathAtBuilder.projectKey(any())).thenReturn(pathAtBuilder); + when(pathAtBuilder.repoSlug(any())).thenReturn(pathAtBuilder); + when(pathAtBuilder.subPath(any())).thenReturn(pathAtBuilder); + when(pathAtBuilder.at(any())).thenReturn(pathAtBuilder); + when(pathAtBuilder.build()).thenReturn(pathAt); + + when(bitbucketService.getLastCommit(pathAt)).thenReturn(Optional.empty()); + + assertThat(projectComponents.getComponents()).containsKey(componentId); + + // when + var exception = assertThrows(RestEntityNotFoundException.class, () -> + provisionerActionsService.deleteComponentProvisioningStatus(projectKey, componentId) + ); + + // then + assertThat(exception.getMessage()).startsWith("No component provisioning status for pathAt:"); + verify(bitbucketService, times(0)).pushFile(any(), any(), anyString()); + } + + @Test + void givenAProjectComponents_whenSaveProjectComponents_andBitbucketRejectAsNoUpdates_ThenExceptionIsIgnored() throws JsonProcessingException { + // given + var pathAt = mock(BitbucketPathAt.class); + var sourceCommitId = "sourceCommitId"; + var updatedProjectComponents = ProjectComponentsMother.of(); + var httpClientErrorException = new HttpClientErrorException(HttpStatus.CONFLICT, "\"{\"errors\":" + + "[{\"context\":null,\"message\":\"The content provided is the same as what already exists. No change was committed.\"" + + ",\"exceptionName\":\"com.atlassian.bitbucket.content.FileContentUnmodifiedException\"}]}\""); + + doThrow(httpClientErrorException).when(bitbucketService).pushFile(eq(pathAt), eq(sourceCommitId), anyString()); + prepareMocksForSave(updatedProjectComponents); + + // when + provisionerActionsService.saveProjectComponents(pathAt, sourceCommitId, updatedProjectComponents); + + // then + // no exception is thrown + } + + @Test + void givenAProjectComponents_whenSaveProjectComponents_andBitbucketReject_ThenExceptionIsThrown() throws JsonProcessingException { + // given + var pathAt = mock(BitbucketPathAt.class); + var sourceCommitId = "sourceCommitId"; + var updatedProjectComponents = ProjectComponentsMother.of(); + var httpClientErrorException = new HttpClientErrorException(HttpStatus.CONFLICT, "Client Error"); + + doThrow(httpClientErrorException).when(bitbucketService).pushFile(eq(pathAt), eq(sourceCommitId), anyString()); + prepareMocksForSave(updatedProjectComponents); + + // when + var exception = assertThrows(HttpClientErrorException.class, () -> + provisionerActionsService.saveProjectComponents(pathAt, sourceCommitId, updatedProjectComponents) + ); + + // then + assertThat(exception).isEqualTo(httpClientErrorException); + } + + @Test + void givenExistingProjectComponents_whenUpdatePartiallyComponentProvisioningStatus_thenBitbucketFileIsUpdated() + throws JsonProcessingException { + + // given + var projectKey = "projectKey"; + var status = Status.FAILED; // any status is allowed + var componentId = "componentId"; + var catalogItemId = "catalogItemId"; + var componentUrl = "componentUrl"; + var parameterPair = Pair.of("paramName", List.of("paramValue")); + var workflowJobId = "workflowJobId"; + + var pathAt = BitbucketPathAtMother.of(); + var sourceCommitId = "sourceCommitId"; + + var projectComponents = ProjectComponentsMother.of(); + var updatedProjectComponents = ProjectComponentsMother.of(); + + // get path + prepareMocksForGetBitbucketPathAt(pathAt); + + // last commit exists + when(bitbucketService.getLastCommit(pathAt)).thenReturn(Optional.of(sourceCommitId)); + + // project components exist + prepareMocksForGetExistingProjectComponents(pathAt, projectComponents); + + // partial update call + when(projectComponentsService.updatePartiallyExistingComponent( + eq(projectComponents), + eq(componentId), + eq(catalogItemId), + eq(status), + eq(componentUrl), + eq(workflowJobId), + any(), + any(), + eq(List.of(Parameter.builder().name("paramName").values(List.of("paramValue")).build())) + )).thenReturn(updatedProjectComponents); + + var serializedUpdatedProjectComponents = prepareMocksForSave(updatedProjectComponents); + + // when + provisionerActionsService.updatePartiallyComponentProvisioningStatus( + projectKey, + status, + componentId, + catalogItemId, + componentUrl, + workflowJobId, + List.of(parameterPair) + ); + + // then + verify(bitbucketService).pushFile( + pathAt, + sourceCommitId, + serializedUpdatedProjectComponents + ); + } + + @Test + void givenNoProjectComponents_whenUpdatePartiallyComponentProvisioningStatus_thenThrowException(){ + + // given + var projectKey = "projectKey"; + var status = Status.FAILED; + var componentId = "componentId"; + var catalogItemId = "catalogItemId"; + var componentUrl = "componentUrl"; + var workflowJobId = "workflowJobId"; + + var pathAt = BitbucketPathAtMother.of(); + + prepareMocksForGetBitbucketPathAt(pathAt); + + // Simulate null projectComponents + when(bitbucketService.getTextFileContents(pathAt)).thenReturn(Optional.empty()); + when(projectComponentsService.createNewComponent()).thenReturn(null); + + // when + var exception = assertThrows(ElementNotFoundException.class, () -> + provisionerActionsService.updatePartiallyComponentProvisioningStatus( + projectKey, + status, + componentId, + catalogItemId, + componentUrl, + workflowJobId, + List.of() + ) + ); + + // then + assertThat(exception.getMessage()) + .isEqualTo("In a partial update, the projectComponent should exist."); + } + + @Test + void givenNewFile_whenUpdatePartiallyComponentProvisioningStatus_thenPushFileIsCalledWithNullCommit() + throws JsonProcessingException { + + // given + var projectKey = "projectKey"; + var status = Status.UNKNOWN; + var componentId = "componentId"; + var catalogItemId = "catalogItemId"; + var componentUrl = "url"; + var workflowJobId = "workflowJobId"; + + var pathAt = BitbucketPathAtMother.of(); + + var projectComponents = ProjectComponentsMother.of(); + var updatedProjectComponents = ProjectComponentsMother.of(); + + prepareMocksForGetBitbucketPathAt(pathAt); + + // no last commit + when(bitbucketService.getLastCommit(pathAt)).thenReturn(Optional.empty()); + + // components exist + prepareMocksForGetExistingProjectComponents(pathAt, projectComponents); + + when(projectComponentsService.updatePartiallyExistingComponent( + eq(projectComponents), + eq(componentId), + eq(catalogItemId), + eq(status), + eq(componentUrl), + eq(workflowJobId), + any(), + any(), + any() + )).thenReturn(updatedProjectComponents); + + var serialized = prepareMocksForSave(updatedProjectComponents); + + // when + provisionerActionsService.updatePartiallyComponentProvisioningStatus( + projectKey, + status, + componentId, + catalogItemId, + componentUrl, + workflowJobId, + List.of() + ); + + // then + verify(bitbucketService).pushFile(pathAt, null, serialized); + } + @Test + void givenExistingComponent_whenUpdate_thenCreatedAtIsPreserved() throws Exception { + // given + var projectKey = "projectKey"; + var componentId = "componentId"; + + var existing = new ProjectComponent(); + existing.setCreatedAt("originalCreatedAt"); + + var components = new HashMap(); + components.put(componentId, existing); + + var projectComponents = new ProjectComponents(); + projectComponents.setComponents(components); + + var pathAt = BitbucketPathAtMother.of(); + + prepareMocksForGetBitbucketPathAt(pathAt); + when(bitbucketService.getLastCommit(pathAt)).thenReturn(Optional.of("commit")); + prepareMocksForGetExistingProjectComponents(pathAt, projectComponents); + + when(projectComponentsService.updateExistingComponent( + any(), any(), any(), any(), any(), any(), any(), any() + )).thenReturn(ProjectComponentsMother.of()); + + prepareMocksForSave(ProjectComponentsMother.of()); + + // when + provisionerActionsService.updateComponentProvisioningStatus( + projectKey, + Status.CREATED, + componentId, + "catalogItemId", + "url", + List.of() + ); + + // then + verify(projectComponentsService).updateExistingComponent( + eq(projectComponents), + eq(componentId), + any(), + any(), + any(), + eq("originalCreatedAt"), // 🔥 clave + any(), + any() + ); + } + + @Test + void givenExistingComponent_whenUpdate_thenUpdatedAtIsGenerated() throws Exception { + // given + var projectKey = "projectKey"; + var componentId = "componentId"; + + var existing = new ProjectComponent(); + existing.setCreatedAt("originalCreatedAt"); + + var components = new HashMap(); + components.put(componentId, existing); + + var projectComponents = new ProjectComponents(); + projectComponents.setComponents(components); + + var pathAt = BitbucketPathAtMother.of(); + + prepareMocksForGetBitbucketPathAt(pathAt); + when(bitbucketService.getLastCommit(pathAt)).thenReturn(Optional.of("commit")); + prepareMocksForGetExistingProjectComponents(pathAt, projectComponents); + + when(projectComponentsService.updateExistingComponent( + any(), any(), any(), any(), any(), any(), any(), any() + )).thenReturn(ProjectComponentsMother.of()); + + prepareMocksForSave(ProjectComponentsMother.of()); + + when(projectComponentsService.updateExistingComponent( + any(), any(), any(), any(), any(), + any(), any(), any() + )).thenReturn(ProjectComponentsMother.of()); + + prepareMocksForSave(ProjectComponentsMother.of()); + + // when + provisionerActionsService.updateComponentProvisioningStatus( + projectKey, + Status.CREATED, + componentId, + "catalogItemId", + "url", + List.of() + ); + + // then + verify(projectComponentsService).updateExistingComponent( + any(), + any(), + any(), + any(), + any(), + any(), + argThat(updated -> { + try { + return Long.parseLong(updated) > 0; + } catch (Exception e) { + return false; + } + }), + any() + ); + } + + @Test + void givenExistingComponent_whenPartialUpdate_thenUpdatedAtIsModified() throws Exception { + // given + var componentId = "componentId"; + + var component = new ProjectComponent(); + component.setUpdatedAt("old"); + + var map = new HashMap(); + map.put(componentId, component); + + var projectComponents = new ProjectComponents(); + projectComponents.setComponents(map); + + var pathAt = BitbucketPathAtMother.of(); + + prepareMocksForGetBitbucketPathAt(pathAt); + when(bitbucketService.getLastCommit(pathAt)).thenReturn(Optional.of("commit")); + + prepareMocksForGetExistingProjectComponents(pathAt, projectComponents); + + when(projectComponentsService.updatePartiallyExistingComponent( + any(), any(), any(), any(), any(), any(), any(), any(), any() + )).thenReturn(ProjectComponentsMother.of()); + + prepareMocksForSave(ProjectComponentsMother.of()); + + // when + provisionerActionsService.updatePartiallyComponentProvisioningStatus( + "projectKey", + Status.FAILED, + componentId, + "catalogItemId", + "url", + "jobId", + List.of() + ); + + // then + assertThat(Long.parseLong(component.getUpdatedAt())).isGreaterThan(0); + } + + private String prepareMocksForSave(ProjectComponents updatedProjectComponents) throws JsonProcessingException { + var serializedUpdatedProjectComponents = "{ serialized-updated-json: true }"; + + when(objectMapper.writerWithDefaultPrettyPrinter()).thenReturn(objectWriter); + when(objectWriter.writeValueAsString(updatedProjectComponents)).thenReturn(serializedUpdatedProjectComponents); + + return serializedUpdatedProjectComponents; + } + + private void prepareMocksForGetNonExistingProjectComponents(BitbucketPathAt bitbucketPathAt, ProjectComponents projectComponents){ + when(bitbucketService.getTextFileContents(bitbucketPathAt)).thenReturn(Optional.empty()); + when(projectComponentsService.createNewComponent()).thenReturn(projectComponents); + } + + private void prepareMocksForGetExistingProjectComponents(BitbucketPathAt bitbucketPathAt, ProjectComponents projectComponents) throws JsonProcessingException { + var serializedProjectComponents = "{ serialized-json: true }"; + var bitbucketFileContent = Pair.of(MediaType.APPLICATION_JSON, serializedProjectComponents); + + when(bitbucketService.getTextFileContents(bitbucketPathAt)).thenReturn(Optional.of(bitbucketFileContent)); + when(objectMapper.readValue(serializedProjectComponents, ProjectComponents.class)).thenReturn(projectComponents); + } + + private void populateProvisionerActionsConfiguration() { + var projectKey = "configuredProjectKey"; + var repoSlug = "repoSlug"; + var subPath = "subPath"; + var subPathToken = "subPathToken"; + var branchName = "branchName"; + + provisionerActionsConfiguration.setProjectKey(projectKey); + provisionerActionsConfiguration.setRepositorySlug(repoSlug); + provisionerActionsConfiguration.setSubPath(subPath); + provisionerActionsConfiguration.setSubPathToken(subPathToken); + provisionerActionsConfiguration.setBranchName(branchName); + } + + private void prepareMocksForGetBitbucketPathAt(BitbucketPathAt bitbucketPathAt) { + when(bitbucketService.pathAtBuilder()).thenReturn(bitbucketPathAtBuilder); + when(bitbucketPathAtBuilder.projectKey(provisionerActionsConfiguration.getProjectKey())).thenReturn(bitbucketPathAtBuilder); + when(bitbucketPathAtBuilder.repoSlug(provisionerActionsConfiguration.getRepositorySlug())).thenReturn(bitbucketPathAtBuilder); + when(bitbucketPathAtBuilder.subPath(provisionerActionsConfiguration.getSubPath())).thenReturn(bitbucketPathAtBuilder); + when(bitbucketPathAtBuilder.at(provisionerActionsConfiguration.getBranchName())).thenReturn(bitbucketPathAtBuilder); + when(bitbucketPathAtBuilder.build()).thenReturn(bitbucketPathAt); + } +} From 93be50c2ee769e78ce0018ef2a8f78604af865c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Risue=C3=B1o?= Date: Tue, 9 Jun 2026 13:08:02 +0200 Subject: [PATCH 4/6] Fix sonar --- .../services/ProjectComponentsService.java | 110 ++++++------------ .../services/ProvisionerActionsService.java | 36 ++++-- .../ProjectComponentUpdateRequest.java | 19 +++ .../ProjectComponentsServiceTest.java | 90 +++++++++----- .../ProvisionerActionsServiceTest.java | 94 +++------------ 5 files changed, 161 insertions(+), 188 deletions(-) create mode 100644 src/main/java/org/opendevstack/component_catalog/server/services/provisioner/ProjectComponentUpdateRequest.java diff --git a/src/main/java/org/opendevstack/component_catalog/server/services/ProjectComponentsService.java b/src/main/java/org/opendevstack/component_catalog/server/services/ProjectComponentsService.java index 0aa97b9..d961b32 100644 --- a/src/main/java/org/opendevstack/component_catalog/server/services/ProjectComponentsService.java +++ b/src/main/java/org/opendevstack/component_catalog/server/services/ProjectComponentsService.java @@ -5,10 +5,7 @@ import org.apache.commons.lang3.StringUtils; import org.opendevstack.component_catalog.server.services.exceptions.InvalidComponentStateException; import org.opendevstack.component_catalog.server.services.exceptions.InvalidEntityException; -import org.opendevstack.component_catalog.server.services.provisioner.Parameter; -import org.opendevstack.component_catalog.server.services.provisioner.ProjectComponent; -import org.opendevstack.component_catalog.server.services.provisioner.ProjectComponents; -import org.opendevstack.component_catalog.server.services.provisioner.Status; +import org.opendevstack.component_catalog.server.services.provisioner.*; import org.springframework.stereotype.Service; import java.util.Base64; @@ -30,29 +27,23 @@ public ProjectComponents createNewComponent() { @SneakyThrows public ProjectComponents addNewComponent(ProjectComponents projectComponents, - String componentId, - String catalogItemId, - Status status, - String componentUrl, - String createdAt, - String updatedAt, - List parameters) { - var catalogItemIdWithoutBranch = getRepoPathFromCatalogItemId(catalogItemId); - var branchReference = getBranchRefFromCatalogItemId(catalogItemId); + ProjectComponentUpdateRequest request) { + var catalogItemIdWithoutBranch = getRepoPathFromCatalogItemId(request.getCatalogItemId()); + var branchReference = getBranchRefFromCatalogItemId(request.getCatalogItemId()); var updatedComponents = Optional.ofNullable(projectComponents.getComponents()) .map(HashMap::new) .orElse(new HashMap<>()); - updatedComponents.put(componentId, ProjectComponent.builder() - .componentId(componentId) + updatedComponents.put(request.getComponentId(), ProjectComponent.builder() + .componentId(request.getComponentId()) .catalogItemId(catalogItemIdWithoutBranch) - .status(status) + .status(request.getStatus()) .catalogItemRef(branchReference) - .componentUrl(componentUrl) - .createdAt(createdAt) - .updatedAt(updatedAt) - .parameters(parameters) + .componentUrl(request.getComponentUrl()) + .createdAt(request.getCreatedAt()) + .updatedAt(request.getUpdatedAt()) + .parameters(request.getParameters()) .build()); var updatedProjectComponents = ProjectComponents.builder() @@ -66,23 +57,17 @@ public ProjectComponents addNewComponent(ProjectComponents projectComponents, @SneakyThrows public ProjectComponents updateExistingComponent(ProjectComponents projectComponents, - String componentId, - String catalogItemId, - Status status, - String componentUrl, - String createdAt, - String updatedAt, - List parameters) { + ProjectComponentUpdateRequest request) { Map components = projectComponents.getComponents(); - if (!components.containsKey(componentId)) { - throw new InvalidComponentStateException("Component with id " + componentId + " does not exist"); + if (!components.containsKey(request.getComponentId())) { + throw new InvalidComponentStateException("Component with id " + request.getComponentId() + " does not exist"); } - var existing = components.get(componentId); - var catalogItemIdWithoutBranch = getRepoPathFromCatalogItemId(catalogItemId); - var branchReference = getBranchRefFromCatalogItemId(catalogItemId); + var existing = components.get(request.getComponentId()); + var catalogItemIdWithoutBranch = getRepoPathFromCatalogItemId(request.getCatalogItemId()); + var branchReference = getBranchRefFromCatalogItemId(request.getCatalogItemId()); if (!existing.getCatalogItemId().equals(catalogItemIdWithoutBranch)) { return projectComponents; @@ -91,16 +76,16 @@ public ProjectComponents updateExistingComponent(ProjectComponents projectCompon ProjectComponent updated = ProjectComponent.builder() .componentId(existing.getComponentId()) .catalogItemId(existing.getCatalogItemId()) - .status(status) + .status(request.getStatus()) .catalogItemRef(branchReference) - .componentUrl(StringUtils.isBlank(componentUrl) ? existing.getComponentUrl() : componentUrl) - .createdAt(createdAt) - .updatedAt(updatedAt) - .parameters(parameters) + .componentUrl(StringUtils.isBlank(request.getComponentUrl()) ? existing.getComponentUrl() : request.getComponentUrl()) + .createdAt(request.getCreatedAt()) + .updatedAt(request.getUpdatedAt()) + .parameters(request.getParameters()) .build(); Map updatedMap = new HashMap<>(components); - updatedMap.put(componentId, updated); + updatedMap.put(request.getComponentId(), updated); return ProjectComponents.builder() .components(updatedMap) @@ -109,32 +94,16 @@ public ProjectComponents updateExistingComponent(ProjectComponents projectCompon @SneakyThrows public ProjectComponents updatePartiallyExistingComponent(ProjectComponents projectComponents, - String componentId, - String catalogItemId, - Status status, - String componentUrl, - String workflowJobId, - String created_at, - String updated_at, - List parameters) { + ProjectComponentUpdateRequest request) { - validateComponentExists(projectComponents, componentId); + validateComponentExists(projectComponents, request.getComponentId()); Map updatedMap = projectComponents.getComponents() .entrySet() .stream() .collect(Collectors.toMap( Map.Entry::getKey, - entry -> updateComponentIfMatch( - entry, - componentId, - catalogItemId, - status, - componentUrl, - workflowJobId, - created_at, - updated_at, - parameters) + entry -> updateComponentIfMatch(entry, request) )); return ProjectComponents.builder() @@ -181,29 +150,22 @@ protected String getRepoPathFromCatalogItemId(String catalogItemId) throws Inval } private ProjectComponent updateComponentIfMatch(Map.Entry entry, - String componentId, - String catalogItemId, - Status status, - String componentUrl, - String workflowJobId, - String createdAt, - String updatedAt, - List parameters) { - - if (!entry.getKey().equals(componentId)) { + ProjectComponentUpdateRequest request) { + + if (!entry.getKey().equals(request.getComponentId())) { return entry.getValue(); // leave unchanged } return ProjectComponent.builder() .componentId(entry.getValue().getComponentId()) .catalogItemId(entry.getValue().getCatalogItemId()) - .status(status) - .catalogItemRef(resolveCatalogItemRef(entry.getValue(), catalogItemId)) - .componentUrl(resolveComponentUrl(entry.getValue(), componentUrl)) - .workflowJobId(resolveWorkflowJobId(entry.getValue(), workflowJobId)) - .createdAt(createdAt) - .updatedAt(updatedAt) - .parameters(resolveParameters(entry.getValue(), parameters)) + .status(request.getStatus()) + .catalogItemRef(resolveCatalogItemRef(entry.getValue(), request.getCatalogItemId())) + .componentUrl(resolveComponentUrl(entry.getValue(), request.getComponentUrl())) + .workflowJobId(resolveWorkflowJobId(entry.getValue(), request.getWorkflowJobId())) + .createdAt(request.getCreatedAt()) + .updatedAt(request.getUpdatedAt()) + .parameters(resolveParameters(entry.getValue(), request.getParameters())) .build(); } diff --git a/src/main/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsService.java b/src/main/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsService.java index 8d178a3..9bac86e 100644 --- a/src/main/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsService.java +++ b/src/main/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsService.java @@ -14,10 +14,7 @@ import org.opendevstack.component_catalog.server.services.exceptions.ComponentAlreadyExistsException; import org.opendevstack.component_catalog.server.services.exceptions.ElementNotFoundException; import org.opendevstack.component_catalog.server.services.exceptions.UnableToDeserializeEntityException; -import org.opendevstack.component_catalog.server.services.provisioner.Parameter; -import org.opendevstack.component_catalog.server.services.provisioner.ProjectComponent; -import org.opendevstack.component_catalog.server.services.provisioner.ProjectComponents; -import org.opendevstack.component_catalog.server.services.provisioner.Status; +import org.opendevstack.component_catalog.server.services.provisioner.*; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.web.client.HttpClientErrorException; @@ -58,21 +55,34 @@ public void updateComponentProvisioningStatus(String projectKey, ProjectComponents updatedProjectComponents; var currentTimestamp = System.currentTimeMillis(); + var request = ProjectComponentUpdateRequest.builder() + .componentId(componentId) + .catalogItemId(catalogItemId) + .status(status) + .componentUrl(componentUrl) + .createdAt("") + .updatedAt("") + .parameters(projectComponentParameters) + .build(); if (existsComponent) { log.trace("Updating componentKey: {} to projectComponents: {}. Status: {}", componentId, projectComponents, status); var createdAt = projectComponents.getComponents().get(componentId).getCreatedAt(); var updatedAt = String.valueOf(currentTimestamp); + request.setCreatedAt(createdAt); + request.setUpdatedAt(updatedAt); updatedProjectComponents = projectComponentsService.updateExistingComponent( - projectComponents, componentId, catalogItemId, status, componentUrl, createdAt, updatedAt, projectComponentParameters); + projectComponents, request); } else { log.trace("Adding new componentKey: {} to projectComponents: {}", componentId, projectComponents); var createdAt = String.valueOf(currentTimestamp); var updatedAt = String.valueOf(currentTimestamp); + request.setCreatedAt(createdAt); + request.setUpdatedAt(updatedAt); updatedProjectComponents = projectComponentsService.addNewComponent( - projectComponents, componentId, catalogItemId, status, componentUrl, createdAt, updatedAt, projectComponentParameters); + projectComponents, request); } // Update file with new status @@ -108,8 +118,20 @@ public void updatePartiallyComponentProvisioningStatus(String projectKey, var createdAt = projectComponents.getComponents().get(componentId).getCreatedAt(); var updatedAt = projectComponents.getComponents().get(componentId).getUpdatedAt(); log.trace("Updating partially componentKey: {} to projectComponents: {}. Status: {}", componentId, projectComponents, status); + + var request = ProjectComponentUpdateRequest.builder() + .componentId(componentId) + .catalogItemId(catalogItemId) + .status(status) + .componentUrl(componentUrl) + .workflowJobId(workflowJobId) + .createdAt(createdAt) + .updatedAt(updatedAt) + .parameters(projectComponentParameters) + .build(); + var updatedProjectComponents = projectComponentsService.updatePartiallyExistingComponent( - projectComponents, componentId, catalogItemId, status, componentUrl, workflowJobId, createdAt, updatedAt, projectComponentParameters); + projectComponents, request); // Update file with new status saveProjectComponents(pathAt, sourceCommitId, updatedProjectComponents); diff --git a/src/main/java/org/opendevstack/component_catalog/server/services/provisioner/ProjectComponentUpdateRequest.java b/src/main/java/org/opendevstack/component_catalog/server/services/provisioner/ProjectComponentUpdateRequest.java new file mode 100644 index 0000000..ff59bfd --- /dev/null +++ b/src/main/java/org/opendevstack/component_catalog/server/services/provisioner/ProjectComponentUpdateRequest.java @@ -0,0 +1,19 @@ +package org.opendevstack.component_catalog.server.services.provisioner; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Builder +@Data +public class ProjectComponentUpdateRequest { + private String componentId; + private String catalogItemId; + private Status status; + private String componentUrl; + private String workflowJobId; + private String createdAt; + private String updatedAt; + private List parameters; +} diff --git a/src/test/java/org/opendevstack/component_catalog/server/services/ProjectComponentsServiceTest.java b/src/test/java/org/opendevstack/component_catalog/server/services/ProjectComponentsServiceTest.java index 0912492..34e9d6f 100644 --- a/src/test/java/org/opendevstack/component_catalog/server/services/ProjectComponentsServiceTest.java +++ b/src/test/java/org/opendevstack/component_catalog/server/services/ProjectComponentsServiceTest.java @@ -1,17 +1,11 @@ package org.opendevstack.component_catalog.server.services; -import org.opendevstack.component_catalog.server.services.exceptions.InvalidComponentStateException; -import org.opendevstack.component_catalog.server.services.provisioner.Parameter; -import org.opendevstack.component_catalog.server.services.provisioner.ProjectComponent; -import org.opendevstack.component_catalog.server.services.provisioner.ProjectComponents; -import org.opendevstack.component_catalog.server.services.provisioner.Status; import org.junit.jupiter.api.Test; +import org.opendevstack.component_catalog.server.services.exceptions.InvalidComponentStateException; +import org.opendevstack.component_catalog.server.services.provisioner.*; import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -21,7 +15,28 @@ class ProjectComponentsServiceTest { private final ProjectComponentsService service = new ProjectComponentsService(); private String base64(String val) { - return java.util.Base64.getUrlEncoder().encodeToString(val.getBytes(StandardCharsets.UTF_8)); + return Base64.getUrlEncoder().encodeToString(val.getBytes(StandardCharsets.UTF_8)); + } + + // helper + private ProjectComponentUpdateRequest request(String componentId, + String catalogItemId, + Status status, + String url, + String workflowJobId, + String createdAt, + String updatedAt, + List params) { + return ProjectComponentUpdateRequest.builder() + .componentId(componentId) + .catalogItemId(catalogItemId) + .status(status) + .componentUrl(url) + .workflowJobId(workflowJobId) + .createdAt(createdAt) + .updatedAt(updatedAt) + .parameters(params) + .build(); } @Test @@ -43,7 +58,10 @@ void givenValidInput_whenAddNewComponent_thenComponentAdded() { String encoded = base64("repo/path?at=refs/heads/main"); //when - ProjectComponents updated = service.addNewComponent(pc, "comp1", encoded, Status.CREATING, "url", "created", "updated", Collections.emptyList()); + ProjectComponents updated = service.addNewComponent( + pc, + request("comp1", encoded, Status.CREATING, "url", null, "created", "updated", Collections.emptyList()) + ); //then assertThat(updated.getComponents()).containsKey("comp1"); @@ -81,8 +99,10 @@ void givenExistingComponent_whenUpdateExistingComponent_thenUpdatedCorrectly() { .build(); //when - ProjectComponents updated = - service.updateExistingComponent(pc, "comp1", encodedFull, Status.CREATED, "newUrl", "created", "updated", parameters); + ProjectComponents updated = service.updateExistingComponent( + pc, + request("comp1", encodedFull, Status.CREATED, "newUrl", null, "created", "updated", parameters) + ); //then ProjectComponent updatedComp = updated.getComponents().get("comp1"); @@ -111,8 +131,10 @@ void givenDifferentRepoPath_whenUpdateExistingComponent_thenDoNotUpdateComponent .build(); //when - ProjectComponents updated = - service.updateExistingComponent(pc, "comp1", encodedFullDifferent, Status.CREATED, "x", "created", "updated", Collections.emptyList()); + ProjectComponents updated = service.updateExistingComponent( + pc, + request("comp1", encodedFullDifferent, Status.CREATED, "x", null, "created", "updated", Collections.emptyList()) + ); //then assertThat(updated.getComponents().get("comp1").getCatalogItemId()) @@ -128,7 +150,10 @@ void givenNonExistingComponent_whenUpdateExistingComponent_thenThrow() { //when //then assertThatThrownBy(() -> - service.updateExistingComponent(pc, "unknown", "zzz", Status.CREATED, "x", "created", "updated", Collections.emptyList())) + service.updateExistingComponent( + pc, + request("unknown", "zzz", Status.CREATED, "x", null, "created", "updated", Collections.emptyList()) + )) .isInstanceOf(InvalidComponentStateException.class); } @@ -156,8 +181,10 @@ void givenExistingComponent_whenUpdatePartially_thenUpdatesOnlyFieldsProvided() .build(); //when - ProjectComponents updated = - service.updatePartiallyExistingComponent(pc, "comp1", encodedFull, Status.CREATED, null, null, "created", "updated", parameters); + ProjectComponents updated = service.updatePartiallyExistingComponent( + pc, + request("comp1", encodedFull, Status.CREATED, null, null, "created", "updated", parameters) + ); //then ProjectComponent result = updated.getComponents().get("comp1"); @@ -178,7 +205,10 @@ void givenNonExistingComponent_whenUpdatePartially_thenThrow() { //when //then assertThatThrownBy(() -> - service.updatePartiallyExistingComponent(pc, "missing", "zzz", Status.CREATED, "x", null, "created", "updated", Collections.emptyList())) + service.updatePartiallyExistingComponent( + pc, + request("missing", "zzz", Status.CREATED, "x", null, "created", "updated", Collections.emptyList()) + )) .isInstanceOf(InvalidComponentStateException.class); } @@ -213,8 +243,10 @@ void givenBlankWorkflowJobId_whenUpdatePartially_thenKeepsExistingWorkflowJobId( .build(); //when - ProjectComponents updated = - service.updatePartiallyExistingComponent(pc, "comp1", null, Status.CREATED, null, "", null, null, Collections.emptyList()); + ProjectComponents updated = service.updatePartiallyExistingComponent( + pc, + request("comp1", null, Status.CREATED, null, "", null, null, Collections.emptyList()) + ); //then assertThat(updated.getComponents().get("comp1").getWorkflowJobId()).isEqualTo("existing-job-id"); @@ -236,8 +268,10 @@ void givenNewWorkflowJobId_whenUpdatePartially_thenUsesNewWorkflowJobId() { .build(); //when - ProjectComponents updated = - service.updatePartiallyExistingComponent(pc, "comp1", null, Status.CREATED, null, "new-job-id", null, null, Collections.emptyList()); + ProjectComponents updated = service.updatePartiallyExistingComponent( + pc, + request("comp1", null, Status.CREATED, null, "new-job-id", null, null, Collections.emptyList()) + ); //then assertThat(updated.getComponents().get("comp1").getWorkflowJobId()).isEqualTo("new-job-id"); @@ -287,14 +321,8 @@ void givenNullTimestamps_whenUpdatePartially_thenTimestampsAreOverwrittenWithNul //when ProjectComponents updated = service.updatePartiallyExistingComponent( pc, - "comp1", - null, - Status.CREATED, - null, - null, - null, - null, - Collections.emptyList()); + request("comp1", null, Status.CREATED, null, null, null, null, Collections.emptyList()) + ); //then ProjectComponent result = updated.getComponents().get("comp1"); diff --git a/src/test/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsServiceTest.java b/src/test/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsServiceTest.java index a744755..791cab2 100644 --- a/src/test/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsServiceTest.java +++ b/src/test/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsServiceTest.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; -import lombok.val; import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -20,23 +19,16 @@ import org.opendevstack.component_catalog.server.services.exceptions.ComponentAlreadyExistsException; import org.opendevstack.component_catalog.server.services.exceptions.ElementNotFoundException; import org.opendevstack.component_catalog.server.services.exceptions.InvalidEntityException; -import org.opendevstack.component_catalog.server.services.provisioner.Parameter; -import org.opendevstack.component_catalog.server.services.provisioner.ProjectComponent; -import org.opendevstack.component_catalog.server.services.provisioner.ProjectComponents; -import org.opendevstack.component_catalog.server.services.provisioner.Status; +import org.opendevstack.component_catalog.server.services.provisioner.*; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.web.client.HttpClientErrorException; -import java.util.Base64; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Optional; +import java.util.*; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) @@ -104,22 +96,13 @@ void givenAProvisionersObject_andCreatingStatus_whenUpdateComponentProvisioningS var parameterParam = Pair.of("parameterName", List.of("parameterValue")); - var parameter = Parameter.builder().name(parameterParam.getLeft()).values(parameterParam.getValue()).build(); - var parameters = List.of(parameter); - prepareMocksForGetBitbucketPathAt(pathAt); when(bitbucketService.getLastCommit(pathAt)).thenReturn(Optional.of(sourceCommitId)); prepareMocksForGetNonExistingProjectComponents(pathAt, projectComponents); when(projectComponentsService.addNewComponent( eq(projectComponents), - eq(componentId), - eq(catalogItemId), - eq(status), - eq(componentUrl), - any(), - any(), - eq(parameters) + any(ProjectComponentUpdateRequest.class) )).thenReturn(updatedProjectComponents); var serializedUpdatedProjectComponents = prepareMocksForSave(updatedProjectComponents); @@ -165,13 +148,7 @@ void givenAProvisionersObject_andSelectedStatuses_whenUpdateComponentProvisionin prepareMocksForGetExistingProjectComponents(pathAt, projectComponents); when(projectComponentsService.updateExistingComponent( eq(projectComponents), - eq(componentId), - eq(catalogItemId), - eq(status), - eq(componentUrl), - any(), - any(), - eq(Collections.emptyList()) + any(ProjectComponentUpdateRequest.class) )).thenReturn(updatedProjectComponents); var serializedUpdatedProjectComponents = prepareMocksForSave(updatedProjectComponents); @@ -393,14 +370,7 @@ void givenExistingProjectComponents_whenUpdatePartiallyComponentProvisioningStat // partial update call when(projectComponentsService.updatePartiallyExistingComponent( eq(projectComponents), - eq(componentId), - eq(catalogItemId), - eq(status), - eq(componentUrl), - eq(workflowJobId), - any(), - any(), - eq(List.of(Parameter.builder().name("paramName").values(List.of("paramValue")).build())) + any(ProjectComponentUpdateRequest.class) )).thenReturn(updatedProjectComponents); var serializedUpdatedProjectComponents = prepareMocksForSave(updatedProjectComponents); @@ -488,14 +458,7 @@ void givenNewFile_whenUpdatePartiallyComponentProvisioningStatus_thenPushFileIsC when(projectComponentsService.updatePartiallyExistingComponent( eq(projectComponents), - eq(componentId), - eq(catalogItemId), - eq(status), - eq(componentUrl), - eq(workflowJobId), - any(), - any(), - any() + any(ProjectComponentUpdateRequest.class) )).thenReturn(updatedProjectComponents); var serialized = prepareMocksForSave(updatedProjectComponents); @@ -517,7 +480,6 @@ void givenNewFile_whenUpdatePartiallyComponentProvisioningStatus_thenPushFileIsC @Test void givenExistingComponent_whenUpdate_thenCreatedAtIsPreserved() throws Exception { // given - var projectKey = "projectKey"; var componentId = "componentId"; var existing = new ProjectComponent(); @@ -536,14 +498,14 @@ void givenExistingComponent_whenUpdate_thenCreatedAtIsPreserved() throws Excepti prepareMocksForGetExistingProjectComponents(pathAt, projectComponents); when(projectComponentsService.updateExistingComponent( - any(), any(), any(), any(), any(), any(), any(), any() + any(), any(ProjectComponentUpdateRequest.class) )).thenReturn(ProjectComponentsMother.of()); prepareMocksForSave(ProjectComponentsMother.of()); // when provisionerActionsService.updateComponentProvisioningStatus( - projectKey, + "projectKey", Status.CREATED, componentId, "catalogItemId", @@ -551,23 +513,15 @@ void givenExistingComponent_whenUpdate_thenCreatedAtIsPreserved() throws Excepti List.of() ); - // then verify(projectComponentsService).updateExistingComponent( eq(projectComponents), - eq(componentId), - any(), - any(), - any(), - eq("originalCreatedAt"), // 🔥 clave - any(), - any() + argThat(req -> "originalCreatedAt".equals(req.getCreatedAt())) ); } @Test void givenExistingComponent_whenUpdate_thenUpdatedAtIsGenerated() throws Exception { // given - var projectKey = "projectKey"; var componentId = "componentId"; var existing = new ProjectComponent(); @@ -586,21 +540,14 @@ void givenExistingComponent_whenUpdate_thenUpdatedAtIsGenerated() throws Excepti prepareMocksForGetExistingProjectComponents(pathAt, projectComponents); when(projectComponentsService.updateExistingComponent( - any(), any(), any(), any(), any(), any(), any(), any() - )).thenReturn(ProjectComponentsMother.of()); - - prepareMocksForSave(ProjectComponentsMother.of()); - - when(projectComponentsService.updateExistingComponent( - any(), any(), any(), any(), any(), - any(), any(), any() + any(), any(ProjectComponentUpdateRequest.class) )).thenReturn(ProjectComponentsMother.of()); prepareMocksForSave(ProjectComponentsMother.of()); // when provisionerActionsService.updateComponentProvisioningStatus( - projectKey, + "projectKey", Status.CREATED, componentId, "catalogItemId", @@ -611,19 +558,13 @@ void givenExistingComponent_whenUpdate_thenUpdatedAtIsGenerated() throws Excepti // then verify(projectComponentsService).updateExistingComponent( any(), - any(), - any(), - any(), - any(), - any(), - argThat(updated -> { + argThat(req -> { try { - return Long.parseLong(updated) > 0; + return Long.parseLong(req.getUpdatedAt()) > 0; } catch (Exception e) { return false; } - }), - any() + }) ); } @@ -649,7 +590,8 @@ void givenExistingComponent_whenPartialUpdate_thenUpdatedAtIsModified() throws E prepareMocksForGetExistingProjectComponents(pathAt, projectComponents); when(projectComponentsService.updatePartiallyExistingComponent( - any(), any(), any(), any(), any(), any(), any(), any(), any() + any(), + any(ProjectComponentUpdateRequest.class) )).thenReturn(ProjectComponentsMother.of()); prepareMocksForSave(ProjectComponentsMother.of()); @@ -713,4 +655,4 @@ private void prepareMocksForGetBitbucketPathAt(BitbucketPathAt bitbucketPathAt) when(bitbucketPathAtBuilder.at(provisionerActionsConfiguration.getBranchName())).thenReturn(bitbucketPathAtBuilder); when(bitbucketPathAtBuilder.build()).thenReturn(bitbucketPathAt); } -} +} \ No newline at end of file From b3d354fec0ab7efa6f0f25a5f88814a27ef01055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Risue=C3=B1o?= Date: Tue, 9 Jun 2026 16:32:42 +0200 Subject: [PATCH 5/6] Refactor multiple parameter calls on status update --- .../ProvisionerActionsApiController.java | 25 +++- .../facade/ProvisionerActionsApiFacade.java | 5 +- .../services/ProjectComponentsService.java | 8 +- .../services/ProvisionerActionsService.java | 77 +++-------- ...uest.java => ProjectComponentRequest.java} | 2 +- .../ProvisionerActionsApiControllerTest.java | 87 +++++++----- .../ProvisionerActionsApiFacadeTest.java | 13 +- .../ProjectComponentsServiceTest.java | 18 +-- .../ProvisionerActionsServiceTest.java | 130 +++++++++--------- 9 files changed, 184 insertions(+), 181 deletions(-) rename src/main/java/org/opendevstack/component_catalog/server/services/provisioner/{ProjectComponentUpdateRequest.java => ProjectComponentRequest.java} (86%) diff --git a/src/main/java/org/opendevstack/component_catalog/server/controllers/ProvisionerActionsApiController.java b/src/main/java/org/opendevstack/component_catalog/server/controllers/ProvisionerActionsApiController.java index 0f4408e..b615681 100644 --- a/src/main/java/org/opendevstack/component_catalog/server/controllers/ProvisionerActionsApiController.java +++ b/src/main/java/org/opendevstack/component_catalog/server/controllers/ProvisionerActionsApiController.java @@ -10,6 +10,7 @@ import org.opendevstack.component_catalog.server.model.ProvisioningDeleteRequest; import org.opendevstack.component_catalog.server.model.ProvisioningStatusUpdateRequest; import org.opendevstack.component_catalog.server.services.ProvisionerActionsService; +import org.opendevstack.component_catalog.server.services.provisioner.ProjectComponentRequest; import org.opendevstack.component_catalog.server.services.provisioner.Status; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; @@ -40,9 +41,15 @@ public ResponseEntity notifyProvisioningStatusUpdate(String projectKey, var normalizedComponentUrl = provisioningStatusUpdateRequest.getComponentUrl().orElse(Strings.EMPTY); var parameters = map(provisioningStatusUpdateRequest); - provisionerActionsService.updateComponentProvisioningStatus(normalizedProjectKey, Status.valueOf(status), - provisioningStatusUpdateRequest.getComponentId(), provisioningStatusUpdateRequest.getCatalogItemId(), - normalizedComponentUrl, parameters); + var request = ProjectComponentRequest.builder() + .componentId(provisioningStatusUpdateRequest.getComponentId()) + .catalogItemId(provisioningStatusUpdateRequest.getCatalogItemId()) + .status(Status.valueOf(status)) + .componentUrl(normalizedComponentUrl) + .parameters(parameters) + .build(); + + provisionerActionsService.updateComponentProvisioningStatus(normalizedProjectKey, request); return ResponseEntity.ok().build(); } @@ -58,9 +65,15 @@ public ResponseEntity notifyProvisioningStatusUpdatePartially(String proje var normalizedComponentUrl = provisioningStatusUpdateRequest.getComponentUrl().orElse(Strings.EMPTY); var parameters = map(provisioningStatusUpdateRequest); - provisionerActionsService.updatePartiallyComponentProvisioningStatus(normalizedProjectKey, Status.valueOf(status), - provisioningStatusUpdateRequest.getComponentId(), provisioningStatusUpdateRequest.getCatalogItemId(), - normalizedComponentUrl, provisioningStatusUpdateRequest.getWorkflowJobId().orElse(""), parameters); + var request = ProjectComponentRequest.builder() + .componentId(provisioningStatusUpdateRequest.getComponentId()) + .catalogItemId(provisioningStatusUpdateRequest.getCatalogItemId()) + .status(Status.valueOf(status)) + .componentUrl(normalizedComponentUrl) + .parameters(parameters) + .build(); + + provisionerActionsService.updatePartiallyComponentProvisioningStatus(normalizedProjectKey, request); return ResponseEntity.ok().build(); } diff --git a/src/main/java/org/opendevstack/component_catalog/server/facade/ProvisionerActionsApiFacade.java b/src/main/java/org/opendevstack/component_catalog/server/facade/ProvisionerActionsApiFacade.java index 7dcd8d6..6ea181d 100644 --- a/src/main/java/org/opendevstack/component_catalog/server/facade/ProvisionerActionsApiFacade.java +++ b/src/main/java/org/opendevstack/component_catalog/server/facade/ProvisionerActionsApiFacade.java @@ -1,6 +1,5 @@ package org.opendevstack.component_catalog.server.facade; -import jakarta.validation.constraints.NotNull; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.tuple.Pair; import org.jspecify.annotations.NonNull; @@ -10,6 +9,7 @@ import org.opendevstack.component_catalog.server.services.ProjectsInfoService; import org.opendevstack.component_catalog.server.services.catalog.CatalogItemUserActionGroupsRestriction; import org.opendevstack.component_catalog.server.services.catalog.common.UserActionEntityRestrictions; +import org.opendevstack.component_catalog.server.services.provisioner.Parameter; import org.opendevstack.component_catalog.server.services.restrictions.evaluators.EvaluationRestrictions; import org.opendevstack.component_catalog.server.services.restrictions.evaluators.GroupsRestrictionsEvaluator; import org.opendevstack.component_catalog.server.services.restrictions.evaluators.RestrictionsParams; @@ -42,9 +42,10 @@ public ProvisionerActionsApiFacade(ProjectsInfoService projectsInfoService, } - public static @NonNull List>> map(ProvisioningStatusUpdateRequest provisioningStatusUpdateRequest) { + public static @NonNull List map(ProvisioningStatusUpdateRequest provisioningStatusUpdateRequest) { return provisioningStatusUpdateRequest.getParameters().stream() .map(parameter -> Pair.of(parameter.getName(), parameter.getValues())) + .map(pair -> Parameter.builder().name(pair.getLeft()).values(pair.getRight()).build()) .toList(); } diff --git a/src/main/java/org/opendevstack/component_catalog/server/services/ProjectComponentsService.java b/src/main/java/org/opendevstack/component_catalog/server/services/ProjectComponentsService.java index d961b32..bdec146 100644 --- a/src/main/java/org/opendevstack/component_catalog/server/services/ProjectComponentsService.java +++ b/src/main/java/org/opendevstack/component_catalog/server/services/ProjectComponentsService.java @@ -27,7 +27,7 @@ public ProjectComponents createNewComponent() { @SneakyThrows public ProjectComponents addNewComponent(ProjectComponents projectComponents, - ProjectComponentUpdateRequest request) { + ProjectComponentRequest request) { var catalogItemIdWithoutBranch = getRepoPathFromCatalogItemId(request.getCatalogItemId()); var branchReference = getBranchRefFromCatalogItemId(request.getCatalogItemId()); @@ -57,7 +57,7 @@ public ProjectComponents addNewComponent(ProjectComponents projectComponents, @SneakyThrows public ProjectComponents updateExistingComponent(ProjectComponents projectComponents, - ProjectComponentUpdateRequest request) { + ProjectComponentRequest request) { Map components = projectComponents.getComponents(); @@ -94,7 +94,7 @@ public ProjectComponents updateExistingComponent(ProjectComponents projectCompon @SneakyThrows public ProjectComponents updatePartiallyExistingComponent(ProjectComponents projectComponents, - ProjectComponentUpdateRequest request) { + ProjectComponentRequest request) { validateComponentExists(projectComponents, request.getComponentId()); @@ -150,7 +150,7 @@ protected String getRepoPathFromCatalogItemId(String catalogItemId) throws Inval } private ProjectComponent updateComponentIfMatch(Map.Entry entry, - ProjectComponentUpdateRequest request) { + ProjectComponentRequest request) { if (!entry.getKey().equals(request.getComponentId())) { return entry.getValue(); // leave unchanged diff --git a/src/main/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsService.java b/src/main/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsService.java index 9bac86e..afc3e5e 100644 --- a/src/main/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsService.java +++ b/src/main/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsService.java @@ -34,53 +34,36 @@ public class ProvisionerActionsService { @Synchronized public void updateComponentProvisioningStatus(String projectKey, - Status status, - String componentId, - String catalogItemId, - String componentUrl, - List>> parameters) throws JsonProcessingException { //componentUrl can be null + ProjectComponentRequest request) throws JsonProcessingException { //componentUrl can be null log.debug("Processing provisioning status for projectKey: {}, status: {}, componentId: {}, catalogItemId: {}, componentUrl: {}", - projectKey, status, componentId, catalogItemId, componentUrl); + projectKey, request.getStatus(), request.getComponentId(), request.getCatalogItemId(), request.getComponentUrl()); var pathAt = getBitbucketPathAt(projectKey); - List projectComponentParameters = map(parameters); var sourceCommitId = bitbucketService.getLastCommit(pathAt).orElse(null); // If no sourceCommitId, that means is a new file var projectComponents = getProjectComponents(projectKey); - validate(projectComponents, componentId, status); + validate(projectComponents, request.getComponentId(), request.getStatus()); - var existsComponent = componentExistsInProjectComponents(projectComponents, componentId); + var existsComponent = componentExistsInProjectComponents(projectComponents, request.getComponentId()); ProjectComponents updatedProjectComponents; - var currentTimestamp = System.currentTimeMillis(); - var request = ProjectComponentUpdateRequest.builder() - .componentId(componentId) - .catalogItemId(catalogItemId) - .status(status) - .componentUrl(componentUrl) - .createdAt("") - .updatedAt("") - .parameters(projectComponentParameters) - .build(); + var currentTimestamp = String.valueOf(System.currentTimeMillis()); + + request.setCreatedAt(currentTimestamp); + request.setUpdatedAt(currentTimestamp); if (existsComponent) { - log.trace("Updating componentKey: {} to projectComponents: {}. Status: {}", componentId, projectComponents, status); + log.trace("Updating componentKey: {} to projectComponents: {}. Status: {}", request.getComponentId(), projectComponents, request.getStatus()); - var createdAt = projectComponents.getComponents().get(componentId).getCreatedAt(); - var updatedAt = String.valueOf(currentTimestamp); + var createdAt = projectComponents.getComponents().get(request.getComponentId()).getCreatedAt(); request.setCreatedAt(createdAt); - request.setUpdatedAt(updatedAt); updatedProjectComponents = projectComponentsService.updateExistingComponent( projectComponents, request); } else { - log.trace("Adding new componentKey: {} to projectComponents: {}", componentId, projectComponents); + log.trace("Adding new componentKey: {} to projectComponents: {}", request.getComponentId(), projectComponents); - var createdAt = String.valueOf(currentTimestamp); - var updatedAt = String.valueOf(currentTimestamp); - request.setCreatedAt(createdAt); - request.setUpdatedAt(updatedAt); updatedProjectComponents = projectComponentsService.addNewComponent( projectComponents, request); } @@ -92,43 +75,25 @@ public void updateComponentProvisioningStatus(String projectKey, @Synchronized public void updatePartiallyComponentProvisioningStatus(String projectKey, - Status status, - String componentId, - String catalogItemId, - String componentUrl, - String workflowJobId, - List>> parameters) throws JsonProcessingException { //componentUrl can be null + ProjectComponentRequest request) throws JsonProcessingException { //componentUrl can be null log.debug("Processing provisioning status for projectKey: {}, status: {}, componentId: {}, catalogItemId: {}, componentUrl: {}", - projectKey, status, componentId, catalogItemId, componentUrl); + projectKey, request.getStatus(), request.getComponentId(), request.getCatalogItemId(), request.getComponentUrl()); var pathAt = getBitbucketPathAt(projectKey); - List projectComponentParameters = map(parameters); var sourceCommitId = bitbucketService.getLastCommit(pathAt).orElse(null); // If no sourceCommitId, that means is a new file var projectComponents = getProjectComponents(projectKey); - if (projectComponents == null || projectComponents.getComponents() == null || !projectComponents.getComponents().containsKey(componentId)) { + if (projectComponents == null || projectComponents.getComponents() == null || !projectComponents.getComponents().containsKey(request.getComponentId())) { throw new ElementNotFoundException("In a partial update, the projectComponent should exist."); } var currentTimestamp = System.currentTimeMillis(); - projectComponents.getComponents().get(componentId).setUpdatedAt(String.valueOf(currentTimestamp)); - - var createdAt = projectComponents.getComponents().get(componentId).getCreatedAt(); - var updatedAt = projectComponents.getComponents().get(componentId).getUpdatedAt(); - log.trace("Updating partially componentKey: {} to projectComponents: {}. Status: {}", componentId, projectComponents, status); - - var request = ProjectComponentUpdateRequest.builder() - .componentId(componentId) - .catalogItemId(catalogItemId) - .status(status) - .componentUrl(componentUrl) - .workflowJobId(workflowJobId) - .createdAt(createdAt) - .updatedAt(updatedAt) - .parameters(projectComponentParameters) - .build(); + request.setUpdatedAt(projectComponents.getComponents().get(request.getComponentId()).getUpdatedAt()); + projectComponents.getComponents().get(request.getComponentId()).setUpdatedAt(String.valueOf(currentTimestamp)); + + log.trace("Updating partially componentKey: {} to projectComponents: {}. Status: {}", request.getComponentId(), projectComponents, request.getStatus()); var updatedProjectComponents = projectComponentsService.updatePartiallyExistingComponent( projectComponents, request); @@ -197,12 +162,6 @@ protected void saveProjectComponents(BitbucketPathAt pathAt, String sourceCommit } } - private static @NonNull List map(List>> parameters) { - return parameters.stream() - .map(pair -> Parameter.builder().name(pair.getLeft()).values(pair.getRight()).build()) - .toList(); - } - private void validateComponentDoesNotExistsWhenCreating(ProjectComponents projectComponents, String componentId) { if (componentExistsInProjectComponents(projectComponents, componentId)) { throw new ComponentAlreadyExistsException("Component with id '" + componentId + "' already exists in the project components."); diff --git a/src/main/java/org/opendevstack/component_catalog/server/services/provisioner/ProjectComponentUpdateRequest.java b/src/main/java/org/opendevstack/component_catalog/server/services/provisioner/ProjectComponentRequest.java similarity index 86% rename from src/main/java/org/opendevstack/component_catalog/server/services/provisioner/ProjectComponentUpdateRequest.java rename to src/main/java/org/opendevstack/component_catalog/server/services/provisioner/ProjectComponentRequest.java index ff59bfd..07d97e8 100644 --- a/src/main/java/org/opendevstack/component_catalog/server/services/provisioner/ProjectComponentUpdateRequest.java +++ b/src/main/java/org/opendevstack/component_catalog/server/services/provisioner/ProjectComponentRequest.java @@ -7,7 +7,7 @@ @Builder @Data -public class ProjectComponentUpdateRequest { +public class ProjectComponentRequest { private String componentId; private String catalogItemId; private Status status; diff --git a/src/test/java/org/opendevstack/component_catalog/server/controllers/ProvisionerActionsApiControllerTest.java b/src/test/java/org/opendevstack/component_catalog/server/controllers/ProvisionerActionsApiControllerTest.java index d70075c..3e60492 100644 --- a/src/test/java/org/opendevstack/component_catalog/server/controllers/ProvisionerActionsApiControllerTest.java +++ b/src/test/java/org/opendevstack/component_catalog/server/controllers/ProvisionerActionsApiControllerTest.java @@ -1,7 +1,6 @@ package org.opendevstack.component_catalog.server.controllers; import com.fasterxml.jackson.core.JsonProcessingException; -import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -11,6 +10,8 @@ import org.opendevstack.component_catalog.server.model.ProvisioningStatusUpdateRequest; import org.opendevstack.component_catalog.server.model.ProvisioningStatusUpdateRequestParametersInner; import org.opendevstack.component_catalog.server.services.ProvisionerActionsService; +import org.opendevstack.component_catalog.server.services.provisioner.Parameter; +import org.opendevstack.component_catalog.server.services.provisioner.ProjectComponentRequest; import org.opendevstack.component_catalog.server.services.provisioner.Status; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -31,6 +32,24 @@ class ProvisionerActionsApiControllerTest { @InjectMocks private ProvisionerActionsApiController provisionerActionsApiController; + // helper + private ProjectComponentRequest request(String componentId, + String catalogItemId, + Status status, + String url, + List params) { + return ProjectComponentRequest.builder() + .componentId(componentId) + .catalogItemId(catalogItemId) + .status(status) + .componentUrl(url) + .workflowJobId(null) + .createdAt(null) + .updatedAt(null) + .parameters(params) + .build(); + } + @Test void givenAProjectKey_whenNotifyProvisioningCompleted_thenServiceIsCalled() throws JsonProcessingException { // given @@ -39,7 +58,12 @@ void givenAProjectKey_whenNotifyProvisioningCompleted_thenServiceIsCalled() thro var componentId = "componentId"; var catalogItemId = "catalogItemId"; var componentUrl = "componentUrl"; - var parameter = ProvisioningStatusUpdateRequestParametersInner.builder() + var parameterInner = ProvisioningStatusUpdateRequestParametersInner.builder() + .name("parameterName") + .values(List.of("parameterValue")) + .build(); + var parametersInner = List.of(parameterInner); + var parameter = Parameter.builder() .name("parameterName") .values(List.of("parameterValue")) .build(); @@ -49,9 +73,7 @@ void givenAProjectKey_whenNotifyProvisioningCompleted_thenServiceIsCalled() thro .componentId(componentId) .catalogItemId(catalogItemId) .componentUrl(componentUrl) - .parameters(parameters); - - var mappedParameters = List.of(Pair.of(parameter.getName(), parameter.getValues())); + .parameters(parametersInner); // when provisionerActionsApiController.notifyProvisioningStatusUpdate(projectKey, status.name(), request); @@ -59,7 +81,8 @@ void givenAProjectKey_whenNotifyProvisioningCompleted_thenServiceIsCalled() thro // then verify(provisionerActionsApiFacade).validateGroupRestrictions(eq(projectKey.toUpperCase())); verify(provisionerActionsService).updateComponentProvisioningStatus(projectKey.toUpperCase(), - status, componentId, catalogItemId, componentUrl, mappedParameters); + request(componentId, catalogItemId, status, componentUrl, parameters) + ); } @Test @@ -70,21 +93,22 @@ void givenAProjectKey_whenNotifyProvisioningStatusUpdatePartially_thenServiceIsC var componentId = "componentId"; var catalogItemId = "catalogItemId"; var componentUrl = "componentUrl"; - var parameter = ProvisioningStatusUpdateRequestParametersInner.builder() + var parameterInner = ProvisioningStatusUpdateRequestParametersInner.builder() + .name("parameterName") + .values(List.of("parameterValue")) + .build(); + var parametersInner = List.of(parameterInner); + var parameter = Parameter.builder() .name("parameterName") .values(List.of("parameterValue")) .build(); - var workflowJobId = "workflowJobId"; var parameters = List.of(parameter); var request = new ProvisioningStatusUpdateRequest() .componentId(componentId) .catalogItemId(catalogItemId) .componentUrl(componentUrl) - .workflowJobId(workflowJobId) - .parameters(parameters); - - var mappedParameters = List.of(Pair.of(parameter.getName(), parameter.getValues())); + .parameters(parametersInner); // when provisionerActionsApiController.notifyProvisioningStatusUpdatePartially(projectKey, status.name(), request); @@ -93,12 +117,7 @@ void givenAProjectKey_whenNotifyProvisioningStatusUpdatePartially_thenServiceIsC verify(provisionerActionsApiFacade).validateGroupRestrictions(eq(projectKey.toUpperCase())); verify(provisionerActionsService).updatePartiallyComponentProvisioningStatus( projectKey.toUpperCase(), - status, - componentId, - catalogItemId, - componentUrl, - workflowJobId, - mappedParameters + request(componentId, catalogItemId, status, componentUrl, parameters) ); } @@ -109,8 +128,12 @@ void givenAProjectKeyAndNoComponentUrl_whenNotifyProvisioningStatusUpdatePartial var status = Status.CREATING; var componentId = "componentId"; var catalogItemId = "catalogItemId"; - var workflowJobId = "workflowJobId"; - var parameter = ProvisioningStatusUpdateRequestParametersInner.builder() + var parameterInner = ProvisioningStatusUpdateRequestParametersInner.builder() + .name("parameterName") + .values(List.of("parameterValue")) + .build(); + var parametersInner = List.of(parameterInner); + var parameter = Parameter.builder() .name("parameterName") .values(List.of("parameterValue")) .build(); @@ -119,10 +142,7 @@ void givenAProjectKeyAndNoComponentUrl_whenNotifyProvisioningStatusUpdatePartial var request = new ProvisioningStatusUpdateRequest() .componentId(componentId) .catalogItemId(catalogItemId) - .workflowJobId(workflowJobId) - .parameters(parameters); - - var mappedParameters = List.of(Pair.of(parameter.getName(), parameter.getValues())); + .parameters(parametersInner); // when provisionerActionsApiController.notifyProvisioningStatusUpdatePartially(projectKey, status.name(), request); @@ -130,17 +150,23 @@ void givenAProjectKeyAndNoComponentUrl_whenNotifyProvisioningStatusUpdatePartial // then verify(provisionerActionsApiFacade).validateGroupRestrictions(eq(projectKey.toUpperCase())); verify(provisionerActionsService).updatePartiallyComponentProvisioningStatus(projectKey.toUpperCase(), - status, componentId, catalogItemId, "", workflowJobId, mappedParameters); + request(componentId, catalogItemId, status, "", parameters) + ); } @Test void givenAProjectKeyAndNoComponentUrl_whenNotifyProvisioningStatusUpdate_thenServiceIsCalledWithEmptyUrl() throws JsonProcessingException { // given var projectKey = "projectKey"; - var status = Status.CREATED; + var status = Status.CREATING; var componentId = "componentId"; var catalogItemId = "catalogItemId"; - var parameter = ProvisioningStatusUpdateRequestParametersInner.builder() + var parameterInner = ProvisioningStatusUpdateRequestParametersInner.builder() + .name("parameterName") + .values(List.of("parameterValue")) + .build(); + var parametersInner = List.of(parameterInner); + var parameter = Parameter.builder() .name("parameterName") .values(List.of("parameterValue")) .build(); @@ -149,9 +175,7 @@ void givenAProjectKeyAndNoComponentUrl_whenNotifyProvisioningStatusUpdate_thenSe var request = new ProvisioningStatusUpdateRequest() .componentId(componentId) .catalogItemId(catalogItemId) - .parameters(parameters); - - var mappedParameters = List.of(Pair.of(parameter.getName(), parameter.getValues())); + .parameters(parametersInner); // when provisionerActionsApiController.notifyProvisioningStatusUpdate(projectKey, status.name(), request); @@ -159,7 +183,8 @@ void givenAProjectKeyAndNoComponentUrl_whenNotifyProvisioningStatusUpdate_thenSe // then verify(provisionerActionsApiFacade).validateGroupRestrictions(eq(projectKey.toUpperCase())); verify(provisionerActionsService).updateComponentProvisioningStatus(projectKey.toUpperCase(), - status, componentId, catalogItemId, "", mappedParameters); + request(componentId, catalogItemId, status, "", parameters) + ); } @Test diff --git a/src/test/java/org/opendevstack/component_catalog/server/facade/ProvisionerActionsApiFacadeTest.java b/src/test/java/org/opendevstack/component_catalog/server/facade/ProvisionerActionsApiFacadeTest.java index b28467e..4f259a3 100644 --- a/src/test/java/org/opendevstack/component_catalog/server/facade/ProvisionerActionsApiFacadeTest.java +++ b/src/test/java/org/opendevstack/component_catalog/server/facade/ProvisionerActionsApiFacadeTest.java @@ -11,6 +11,7 @@ import org.opendevstack.component_catalog.server.model.ProvisioningStatusUpdateRequest; import org.opendevstack.component_catalog.server.model.ProvisioningStatusUpdateRequestParametersInner; import org.opendevstack.component_catalog.server.services.ProjectsInfoService; +import org.opendevstack.component_catalog.server.services.provisioner.Parameter; import org.opendevstack.component_catalog.server.services.restrictions.evaluators.EvaluationRestrictions; import org.opendevstack.component_catalog.server.services.restrictions.evaluators.GroupsRestrictionsEvaluator; import org.opendevstack.component_catalog.server.services.restrictions.evaluators.RestrictionsParams; @@ -50,7 +51,7 @@ void setUp() { } @Test - void map_convertsParametersToPairs() { + void map_convertsParametersToPlainParameter() { // given var parameter1 = ProvisioningStatusUpdateRequestParametersInner.builder() .name("param1") @@ -68,8 +69,14 @@ void map_convertsParametersToPairs() { // then assertThat(result).containsExactly( - Pair.of("param1", List.of("value1", "value2")), - Pair.of("param2", List.of("value3")) + Parameter.builder() + .name("param1") + .values(List.of("value1", "value2")) + .build(), + Parameter.builder() + .name("param2") + .values(List.of("value3")) + .build() ); } diff --git a/src/test/java/org/opendevstack/component_catalog/server/services/ProjectComponentsServiceTest.java b/src/test/java/org/opendevstack/component_catalog/server/services/ProjectComponentsServiceTest.java index 34e9d6f..4ffae20 100644 --- a/src/test/java/org/opendevstack/component_catalog/server/services/ProjectComponentsServiceTest.java +++ b/src/test/java/org/opendevstack/component_catalog/server/services/ProjectComponentsServiceTest.java @@ -19,15 +19,15 @@ private String base64(String val) { } // helper - private ProjectComponentUpdateRequest request(String componentId, - String catalogItemId, - Status status, - String url, - String workflowJobId, - String createdAt, - String updatedAt, - List params) { - return ProjectComponentUpdateRequest.builder() + private ProjectComponentRequest request(String componentId, + String catalogItemId, + Status status, + String url, + String workflowJobId, + String createdAt, + String updatedAt, + List params) { + return ProjectComponentRequest.builder() .componentId(componentId) .catalogItemId(catalogItemId) .status(status) diff --git a/src/test/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsServiceTest.java b/src/test/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsServiceTest.java index 791cab2..56e3618 100644 --- a/src/test/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsServiceTest.java +++ b/src/test/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsServiceTest.java @@ -24,7 +24,10 @@ import org.springframework.http.MediaType; import org.springframework.web.client.HttpClientErrorException; -import java.util.*; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -53,6 +56,27 @@ class ProvisionerActionsServiceTest { private ProvisionerActionsService provisionerActionsService; + // helper + private ProjectComponentRequest request(String componentId, + String catalogItemId, + Status status, + String url, + String workflowJobId, + String createdAt, + String updatedAt, + List params) { + return ProjectComponentRequest.builder() + .componentId(componentId) + .catalogItemId(catalogItemId) + .status(status) + .componentUrl(url) + .workflowJobId(workflowJobId) + .createdAt(createdAt) + .updatedAt(updatedAt) + .parameters(params) + .build(); + } + @BeforeEach void setUp() { provisionerActionsConfiguration = new ProvisionerActionsConfiguration(); @@ -87,6 +111,8 @@ void givenAProvisionersObject_andCreatingStatus_whenUpdateComponentProvisioningS var componentId = "componentId"; var catalogItemId = "catalogItemId"; var componentUrl = "catalogUrl"; + var createdAt = "created"; + var updatedAt = "updated"; var pathAt = BitbucketPathAtMother.of(); var sourceCommitId = "sourceCommitId"; @@ -94,7 +120,7 @@ void givenAProvisionersObject_andCreatingStatus_whenUpdateComponentProvisioningS var projectComponents = new ProjectComponents(); var updatedProjectComponents = ProjectComponentsMother.of(); - var parameterParam = Pair.of("parameterName", List.of("parameterValue")); + var parameters = List.of(Parameter.builder().name("parameterName").values(List.of("parameterValue")).build()); prepareMocksForGetBitbucketPathAt(pathAt); when(bitbucketService.getLastCommit(pathAt)).thenReturn(Optional.of(sourceCommitId)); @@ -102,19 +128,15 @@ void givenAProvisionersObject_andCreatingStatus_whenUpdateComponentProvisioningS prepareMocksForGetNonExistingProjectComponents(pathAt, projectComponents); when(projectComponentsService.addNewComponent( eq(projectComponents), - any(ProjectComponentUpdateRequest.class) + any(ProjectComponentRequest.class) )).thenReturn(updatedProjectComponents); - var serializedUpdatedProjectComponents = prepareMocksForSave(updatedProjectComponents); + var serializedUpdatedProjectComponents = prepareMocksForSave(); // when provisionerActionsService.updateComponentProvisioningStatus( projectKey, - status, - componentId, - catalogItemId, - componentUrl, - List.of(parameterParam) + request(componentId, catalogItemId, status, componentUrl, null, createdAt, updatedAt, parameters) ); // then @@ -135,6 +157,8 @@ void givenAProvisionersObject_andSelectedStatuses_whenUpdateComponentProvisionin var componentId = "componentId"; var catalogItemId = "catalogItemId"; var componentUrl = "catalogUrl"; + var createdAt = "created"; + var updatedAt = "updated"; var pathAt = BitbucketPathAtMother.of(); var sourceCommitId = "sourceCommitId"; @@ -148,19 +172,15 @@ void givenAProvisionersObject_andSelectedStatuses_whenUpdateComponentProvisionin prepareMocksForGetExistingProjectComponents(pathAt, projectComponents); when(projectComponentsService.updateExistingComponent( eq(projectComponents), - any(ProjectComponentUpdateRequest.class) + any(ProjectComponentRequest.class) )).thenReturn(updatedProjectComponents); - var serializedUpdatedProjectComponents = prepareMocksForSave(updatedProjectComponents); + var serializedUpdatedProjectComponents = prepareMocksForSave(); // when provisionerActionsService.updateComponentProvisioningStatus( projectKey, - status, - componentId, - catalogItemId, - componentUrl, - Collections.emptyList() + request(componentId, catalogItemId, status, componentUrl, null, createdAt, updatedAt, List.of()) ); // then @@ -258,7 +278,7 @@ void givenAProjectKey_andAComponentId_whenDeleteComponentProvisioningStatus_then when(projectComponentsService.deleteComponent(projectComponents, componentId)).thenReturn(projectComponentsWithoutComponentId); prepareMocksForGetExistingProjectComponents(pathAt, projectComponents); - var serializedProjectComponentsWithoutComponentId = prepareMocksForSave(projectComponentsWithoutComponentId); + var serializedProjectComponentsWithoutComponentId = prepareMocksForSave(); // when provisionerActionsService.deleteComponentProvisioningStatus(projectKey, componentId); @@ -310,7 +330,7 @@ void givenAProjectComponents_whenSaveProjectComponents_andBitbucketRejectAsNoUpd ",\"exceptionName\":\"com.atlassian.bitbucket.content.FileContentUnmodifiedException\"}]}\""); doThrow(httpClientErrorException).when(bitbucketService).pushFile(eq(pathAt), eq(sourceCommitId), anyString()); - prepareMocksForSave(updatedProjectComponents); + prepareMocksForSave(); // when provisionerActionsService.saveProjectComponents(pathAt, sourceCommitId, updatedProjectComponents); @@ -328,7 +348,7 @@ void givenAProjectComponents_whenSaveProjectComponents_andBitbucketReject_ThenEx var httpClientErrorException = new HttpClientErrorException(HttpStatus.CONFLICT, "Client Error"); doThrow(httpClientErrorException).when(bitbucketService).pushFile(eq(pathAt), eq(sourceCommitId), anyString()); - prepareMocksForSave(updatedProjectComponents); + prepareMocksForSave(); // when var exception = assertThrows(HttpClientErrorException.class, () -> @@ -349,8 +369,10 @@ void givenExistingProjectComponents_whenUpdatePartiallyComponentProvisioningStat var componentId = "componentId"; var catalogItemId = "catalogItemId"; var componentUrl = "componentUrl"; - var parameterPair = Pair.of("paramName", List.of("paramValue")); + var parameterList = List.of(Parameter.builder().name("paramName").values(List.of("paramValue")).build()); var workflowJobId = "workflowJobId"; + var createdAt = "created"; + var updatedAt = "updated"; var pathAt = BitbucketPathAtMother.of(); var sourceCommitId = "sourceCommitId"; @@ -370,20 +392,15 @@ void givenExistingProjectComponents_whenUpdatePartiallyComponentProvisioningStat // partial update call when(projectComponentsService.updatePartiallyExistingComponent( eq(projectComponents), - any(ProjectComponentUpdateRequest.class) + any(ProjectComponentRequest.class) )).thenReturn(updatedProjectComponents); - var serializedUpdatedProjectComponents = prepareMocksForSave(updatedProjectComponents); + var serializedUpdatedProjectComponents = prepareMocksForSave(); // when provisionerActionsService.updatePartiallyComponentProvisioningStatus( projectKey, - status, - componentId, - catalogItemId, - componentUrl, - workflowJobId, - List.of(parameterPair) + request(componentId, catalogItemId, status, componentUrl, workflowJobId, createdAt, updatedAt, parameterList) ); // then @@ -404,6 +421,8 @@ void givenNoProjectComponents_whenUpdatePartiallyComponentProvisioningStatus_the var catalogItemId = "catalogItemId"; var componentUrl = "componentUrl"; var workflowJobId = "workflowJobId"; + var createdAt = "created"; + var updatedAt = "updated"; var pathAt = BitbucketPathAtMother.of(); @@ -417,12 +436,7 @@ void givenNoProjectComponents_whenUpdatePartiallyComponentProvisioningStatus_the var exception = assertThrows(ElementNotFoundException.class, () -> provisionerActionsService.updatePartiallyComponentProvisioningStatus( projectKey, - status, - componentId, - catalogItemId, - componentUrl, - workflowJobId, - List.of() + request(componentId, catalogItemId, status, componentUrl, workflowJobId, createdAt, updatedAt, List.of()) ) ); @@ -442,6 +456,8 @@ void givenNewFile_whenUpdatePartiallyComponentProvisioningStatus_thenPushFileIsC var catalogItemId = "catalogItemId"; var componentUrl = "url"; var workflowJobId = "workflowJobId"; + var createdAt = "created"; + var updatedAt = "updated"; var pathAt = BitbucketPathAtMother.of(); @@ -458,20 +474,15 @@ void givenNewFile_whenUpdatePartiallyComponentProvisioningStatus_thenPushFileIsC when(projectComponentsService.updatePartiallyExistingComponent( eq(projectComponents), - any(ProjectComponentUpdateRequest.class) + any(ProjectComponentRequest.class) )).thenReturn(updatedProjectComponents); - var serialized = prepareMocksForSave(updatedProjectComponents); + var serialized = prepareMocksForSave(); // when provisionerActionsService.updatePartiallyComponentProvisioningStatus( projectKey, - status, - componentId, - catalogItemId, - componentUrl, - workflowJobId, - List.of() + request(componentId, catalogItemId, status, componentUrl, workflowJobId, createdAt, updatedAt, List.of()) ); // then @@ -498,19 +509,15 @@ void givenExistingComponent_whenUpdate_thenCreatedAtIsPreserved() throws Excepti prepareMocksForGetExistingProjectComponents(pathAt, projectComponents); when(projectComponentsService.updateExistingComponent( - any(), any(ProjectComponentUpdateRequest.class) + any(), any(ProjectComponentRequest.class) )).thenReturn(ProjectComponentsMother.of()); - prepareMocksForSave(ProjectComponentsMother.of()); + prepareMocksForSave(); // when provisionerActionsService.updateComponentProvisioningStatus( "projectKey", - Status.CREATED, - componentId, - "catalogItemId", - "url", - List.of() + request(componentId, "catalogItemId", Status.CREATED, "url", null, "created", "updated", List.of()) ); verify(projectComponentsService).updateExistingComponent( @@ -540,19 +547,15 @@ void givenExistingComponent_whenUpdate_thenUpdatedAtIsGenerated() throws Excepti prepareMocksForGetExistingProjectComponents(pathAt, projectComponents); when(projectComponentsService.updateExistingComponent( - any(), any(ProjectComponentUpdateRequest.class) + any(), any(ProjectComponentRequest.class) )).thenReturn(ProjectComponentsMother.of()); - prepareMocksForSave(ProjectComponentsMother.of()); + prepareMocksForSave(); // when provisionerActionsService.updateComponentProvisioningStatus( "projectKey", - Status.CREATED, - componentId, - "catalogItemId", - "url", - List.of() + request(componentId, "catalogItemId", Status.CREATED, "url", null, "created", "updated", List.of()) ); // then @@ -591,31 +594,26 @@ void givenExistingComponent_whenPartialUpdate_thenUpdatedAtIsModified() throws E when(projectComponentsService.updatePartiallyExistingComponent( any(), - any(ProjectComponentUpdateRequest.class) + any(ProjectComponentRequest.class) )).thenReturn(ProjectComponentsMother.of()); - prepareMocksForSave(ProjectComponentsMother.of()); + prepareMocksForSave(); // when provisionerActionsService.updatePartiallyComponentProvisioningStatus( "projectKey", - Status.FAILED, - componentId, - "catalogItemId", - "url", - "jobId", - List.of() + request(componentId, "catalogItemId", Status.FAILED, "url", "jobId", "created", "updated", List.of()) ); // then assertThat(Long.parseLong(component.getUpdatedAt())).isGreaterThan(0); } - private String prepareMocksForSave(ProjectComponents updatedProjectComponents) throws JsonProcessingException { + private String prepareMocksForSave() throws JsonProcessingException { var serializedUpdatedProjectComponents = "{ serialized-updated-json: true }"; when(objectMapper.writerWithDefaultPrettyPrinter()).thenReturn(objectWriter); - when(objectWriter.writeValueAsString(updatedProjectComponents)).thenReturn(serializedUpdatedProjectComponents); + when(objectWriter.writeValueAsString(any())).thenReturn(serializedUpdatedProjectComponents); return serializedUpdatedProjectComponents; } From 5ae1f00100342af7afc268228d2553e7c3a55746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Risue=C3=B1o?= Date: Wed, 10 Jun 2026 08:11:52 +0200 Subject: [PATCH 6/6] Improve readability of two methods --- .../server/facade/ProvisionerActionsApiFacade.java | 3 +-- .../server/services/ProvisionerActionsService.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/opendevstack/component_catalog/server/facade/ProvisionerActionsApiFacade.java b/src/main/java/org/opendevstack/component_catalog/server/facade/ProvisionerActionsApiFacade.java index 6ea181d..ac90d86 100644 --- a/src/main/java/org/opendevstack/component_catalog/server/facade/ProvisionerActionsApiFacade.java +++ b/src/main/java/org/opendevstack/component_catalog/server/facade/ProvisionerActionsApiFacade.java @@ -44,8 +44,7 @@ public ProvisionerActionsApiFacade(ProjectsInfoService projectsInfoService, public static @NonNull List map(ProvisioningStatusUpdateRequest provisioningStatusUpdateRequest) { return provisioningStatusUpdateRequest.getParameters().stream() - .map(parameter -> Pair.of(parameter.getName(), parameter.getValues())) - .map(pair -> Parameter.builder().name(pair.getLeft()).values(pair.getRight()).build()) + .map(param -> Parameter.builder().name(param.getName()).values(param.getValues()).build()) .toList(); } diff --git a/src/main/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsService.java b/src/main/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsService.java index afc3e5e..6c82e04 100644 --- a/src/main/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsService.java +++ b/src/main/java/org/opendevstack/component_catalog/server/services/ProvisionerActionsService.java @@ -51,7 +51,6 @@ public void updateComponentProvisioningStatus(String projectKey, var currentTimestamp = String.valueOf(System.currentTimeMillis()); - request.setCreatedAt(currentTimestamp); request.setUpdatedAt(currentTimestamp); if (existsComponent) { @@ -64,6 +63,7 @@ public void updateComponentProvisioningStatus(String projectKey, } else { log.trace("Adding new componentKey: {} to projectComponents: {}", request.getComponentId(), projectComponents); + request.setCreatedAt(currentTimestamp); updatedProjectComponents = projectComponentsService.addNewComponent( projectComponents, request); }