diff --git a/api-project-platform/pom.xml b/api-project-platform/pom.xml index 1c15ede..b2b2b23 100644 --- a/api-project-platform/pom.xml +++ b/api-project-platform/pom.xml @@ -20,6 +20,16 @@ spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + + org.springframework.boot @@ -77,6 +87,10 @@ spring-boot-starter-test test + + org.springframework.security + spring-security-core + diff --git a/api-project-platform/src/main/java/org/opendevstack/apiservice/projectplatform/facade/impl/ProjectsFacadeImpl.java b/api-project-platform/src/main/java/org/opendevstack/apiservice/projectplatform/facade/impl/ProjectsFacadeImpl.java index c30f824..955fe90 100644 --- a/api-project-platform/src/main/java/org/opendevstack/apiservice/projectplatform/facade/impl/ProjectsFacadeImpl.java +++ b/api-project-platform/src/main/java/org/opendevstack/apiservice/projectplatform/facade/impl/ProjectsFacadeImpl.java @@ -1,5 +1,6 @@ package org.opendevstack.apiservice.projectplatform.facade.impl; +import lombok.extern.slf4j.Slf4j; import org.opendevstack.apiservice.externalservice.projectsinfoservice.exception.ProjectsInfoServiceException; import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.Platforms; import org.opendevstack.apiservice.externalservice.projectsinfoservice.service.ProjectsInfoService; @@ -10,6 +11,7 @@ import org.springframework.stereotype.Component; @Component +@Slf4j public class ProjectsFacadeImpl implements ProjectsFacade { private final ProjectsInfoService projectsInfoService; @@ -30,4 +32,5 @@ public ProjectPlatforms getProjectPlatforms(String projectKey) throws ProjectPla throw new ProjectPlatformsException("Failed to retrieve project platforms", e); } } + } diff --git a/api-project-platform/src/main/java/org/opendevstack/apiservice/projectplatform/mapper/ProjectPlatformsMapper.java b/api-project-platform/src/main/java/org/opendevstack/apiservice/projectplatform/mapper/ProjectPlatformsMapper.java index de07a84..9b9b0af 100644 --- a/api-project-platform/src/main/java/org/opendevstack/apiservice/projectplatform/mapper/ProjectPlatformsMapper.java +++ b/api-project-platform/src/main/java/org/opendevstack/apiservice/projectplatform/mapper/ProjectPlatformsMapper.java @@ -31,9 +31,9 @@ public ProjectPlatforms toApiModel(Platforms externalPlatforms) { ProjectPlatforms apiPlatforms = new ProjectPlatforms(); // Map sections - if (externalPlatforms.getSections() != null) { + if (externalPlatforms.sections() != null) { apiPlatforms.setSections( - externalPlatforms.getSections().stream() + externalPlatforms.sections().stream() .map(this::toApiSection) .collect(Collectors.toList()) ); @@ -55,13 +55,13 @@ private Section toApiSection(PlatformSection externalSection) { } Section apiSection = new Section(); - apiSection.setSection(externalSection.getSection()); - apiSection.setTooltip(externalSection.getTooltip()); + apiSection.setSection(externalSection.section()); + apiSection.setTooltip(externalSection.tooltip()); // Map links - if (externalSection.getLinks() != null) { + if (externalSection.links() != null) { apiSection.setLinks( - externalSection.getLinks().stream() + externalSection.links().stream() .map(this::toApiLink) .collect(Collectors.toList()) ); diff --git a/api-project-platform/src/test/java/org/opendevstack/apiservice/projectplatform/facade/impl/ProjectsFacadeImplTest.java b/api-project-platform/src/test/java/org/opendevstack/apiservice/projectplatform/facade/impl/ProjectsFacadeImplTest.java index 9949664..d4e7e2b 100644 --- a/api-project-platform/src/test/java/org/opendevstack/apiservice/projectplatform/facade/impl/ProjectsFacadeImplTest.java +++ b/api-project-platform/src/test/java/org/opendevstack/apiservice/projectplatform/facade/impl/ProjectsFacadeImplTest.java @@ -1,127 +1,82 @@ package org.opendevstack.apiservice.projectplatform.facade.impl; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; import org.opendevstack.apiservice.externalservice.projectsinfoservice.exception.ProjectsInfoServiceException; +import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.PlatformSection; import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.Platforms; import org.opendevstack.apiservice.externalservice.projectsinfoservice.service.ProjectsInfoService; import org.opendevstack.apiservice.projectplatform.exception.ProjectPlatformsException; import org.opendevstack.apiservice.projectplatform.mapper.ProjectPlatformsMapper; -import org.opendevstack.apiservice.projectplatform.model.Link; import org.opendevstack.apiservice.projectplatform.model.ProjectPlatforms; -import org.opendevstack.apiservice.projectplatform.model.Section; +import org.springframework.security.core.context.SecurityContextHolder; import java.util.List; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; -@ExtendWith(MockitoExtension.class) class ProjectsFacadeImplTest { - @Mock private ProjectsInfoService projectsInfoService; - - @Mock private ProjectPlatformsMapper mapper; - private ProjectsFacadeImpl facade; + private ProjectsFacadeImpl sut; @BeforeEach - void setUp() { - facade = new ProjectsFacadeImpl(projectsInfoService, mapper); - } - - @Test - void givenAnyProjectKey_whenGetProjectPlatforms_thenGetMockProjectPlatforms() throws ProjectsInfoServiceException, ProjectPlatformsException { - // Arrange - String projectKey = "DEVSTACK"; + void setup() { + projectsInfoService = mock(ProjectsInfoService.class); + mapper = mock(ProjectPlatformsMapper.class); - // Create external service response - Platforms externalPlatforms = new Platforms(); + sut = new ProjectsFacadeImpl(projectsInfoService, mapper); - // Create expected API response - ProjectPlatforms expectedPlatforms = createExpectedProjectPlatforms(); + // Reset security context before each test + SecurityContextHolder.clearContext(); + } - // Mock behavior - when(projectsInfoService.getProjectPlatforms(projectKey)).thenReturn(externalPlatforms); - when(mapper.toApiModel(externalPlatforms)).thenReturn(expectedPlatforms); - - // Act - ProjectPlatforms result = facade.getProjectPlatforms(projectKey); - - // Assert - assertNotNull(result, "Result should not be null"); - - List
sections = result.getSections(); - assertNotNull(sections, "Sections should not be null"); - assertEquals(3, sections.size(), "There should be 3 sections"); - - // Validate first section - Section appPlatformSection = sections.get(0); - assertEquals("Project Shortcuts - Application Platform", appPlatformSection.getSection()); - assertEquals(4, appPlatformSection.getLinks().size()); - assertTrue(appPlatformSection.getLinks().stream().anyMatch(link -> link.getLabel().equals("JIRA"))); - assertTrue(appPlatformSection.getLinks().stream().allMatch(link -> link.getUrl().equals("https://www.google.com"))); - - // Validate second section - Section dataPlatformSection = sections.get(1); - assertEquals("Project Shortcuts - Data Platform", dataPlatformSection.getSection()); - assertEquals(2, dataPlatformSection.getLinks().size()); - - // Validate third section - Section servicesSection = sections.get(2); - assertEquals("Services", servicesSection.getSection()); - assertEquals(3, servicesSection.getLinks().size()); - - // Verify interactions - verify(projectsInfoService, times(1)).getProjectPlatforms(projectKey); - verify(mapper, times(1)).toApiModel(externalPlatforms); + @AfterEach + void cleanup() { + SecurityContextHolder.clearContext(); } @Test - void givenProjectsInfoServiceThrowsException_whenGetProjectPlatforms_thenRuntimeExceptionIsThrown() throws ProjectsInfoServiceException { - // Arrange - String projectKey = "DEVSTACK"; - when(projectsInfoService.getProjectPlatforms(projectKey)) - .thenThrow(new ProjectsInfoServiceException("Service error")); + void getProjectPlatforms_whenGetProjectPlatforms_thenReturnMappedApiModel() throws Exception { + //given + String projectKey = "PROJ"; - // Act & Assert - ProjectPlatformsException exception = assertThrows(ProjectPlatformsException.class, () -> facade.getProjectPlatforms(projectKey)); + List sections = List.of(); + Platforms externalPlatforms = new Platforms(sections); + when(projectsInfoService.getProjectPlatforms(projectKey)).thenReturn(externalPlatforms); - assertEquals("Failed to retrieve project platforms", exception.getMessage()); - verify(projectsInfoService, times(1)).getProjectPlatforms(projectKey); - verify(mapper, never()).toApiModel(any()); - } + ProjectPlatforms mapped = new ProjectPlatforms(); + when(mapper.toApiModel(externalPlatforms)).thenReturn(mapped); - private ProjectPlatforms createExpectedProjectPlatforms() { - ProjectPlatforms platforms = new ProjectPlatforms(); + //when + ProjectPlatforms result = sut.getProjectPlatforms(projectKey); - // Set sections - Section appPlatformSection = new Section("Project Shortcuts - Application Platform", "tooltip", List.of( - new Link("JIRA", "https://www.google.com", "tooltip", "type", "abbreviation", false), - new Link("Bitbucket", "https://www.google.com", "tooltip", "type", "abbreviation", false), - new Link("Confluence", "https://www.google.com", "tooltip", "type", "abbreviation", false), - new Link("Jenkins", "https://www.google.com", "tooltip", "type", "abbreviation", false) - )); + //then + verify(projectsInfoService).getProjectPlatforms(projectKey); + verify(mapper).toApiModel(externalPlatforms); - Section dataPlatformSection = new Section("Project Shortcuts - Data Platform", "tooltip", List.of( - new Link("EKG", "https://www.google.com", "tooltip", "type", "abbreviation", false), - new Link("EDGC", "https://www.google.com", "tooltip", "type", "abbreviation", false) - )); + assertThat(result).isSameAs(mapped); + } - Section servicesSection = new Section("Services", "tooltip", List.of( - new Link("Service Onboarding", "https://www.google.com", "tooltip", "type", "abbreviation", false), - new Link("Documentation", "https://www.google.com", "tooltip", "type", "abbreviation", false), - new Link("Service Training", "https://www.google.com", "tooltip", "type", "abbreviation", false) - )); + @Test + void getProjectPlatforms_whenInfoServiceThrowsException_thenWrapInProjectPlatformsException() throws Exception { + //given + String projectKey = "PROJ"; - platforms.setSections(List.of(appPlatformSection, dataPlatformSection, servicesSection)); + when(projectsInfoService.getProjectPlatforms(projectKey)) + .thenThrow(new ProjectsInfoServiceException("boom")); - return platforms; + //when/then + assertThatThrownBy(() -> sut.getProjectPlatforms(projectKey)) + .isInstanceOf(ProjectPlatformsException.class) + .hasMessageContaining("Failed to retrieve project platforms"); } -} +} \ No newline at end of file diff --git a/api-project-platform/src/test/java/org/opendevstack/apiservice/projectplatform/mapper/ProjectPlatformsMapperTest.java b/api-project-platform/src/test/java/org/opendevstack/apiservice/projectplatform/mapper/ProjectPlatformsMapperTest.java index 47b2f13..58f3a99 100644 --- a/api-project-platform/src/test/java/org/opendevstack/apiservice/projectplatform/mapper/ProjectPlatformsMapperTest.java +++ b/api-project-platform/src/test/java/org/opendevstack/apiservice/projectplatform/mapper/ProjectPlatformsMapperTest.java @@ -1,21 +1,18 @@ package org.opendevstack.apiservice.projectplatform.mapper; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.PlatformSection; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.PlatformSectionLink; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.Platforms; -import org.opendevstack.apiservice.projectplatform.model.ProjectPlatforms; -import org.opendevstack.apiservice.projectplatform.model.Section; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.junit.jupiter.MockitoExtension; +import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.PlatformSection; +import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.PlatformSectionLink; +import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.Platforms; +import org.opendevstack.apiservice.projectplatform.model.ProjectPlatforms; +import org.opendevstack.apiservice.projectplatform.model.Section; -import java.net.URI; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -73,27 +70,7 @@ void testToApiModel_WithCompleteData_MapsAllFields() { @Test void testToApiModel_WithNullPlatformLinks_MapsOtherFields() { // Given - Platforms externalPlatforms = - new Platforms(); - externalPlatforms.setSections(new ArrayList<>()); - - // When - ProjectPlatforms result = mapper.toApiModel(externalPlatforms); - - // Then - assertNotNull(result); - assertNotNull(result.getSections()); - assertTrue(result.getSections().isEmpty()); - } - - @Test - void testToApiModel_WithNullDisabledPlatforms_MapsOtherFields() { - // Given - Platforms externalPlatforms = - new Platforms(); - Map platformLinks = new HashMap<>(); - platformLinks.put("github", URI.create("https://github.com")); - externalPlatforms.setSections(new ArrayList<>()); + Platforms externalPlatforms = new Platforms(new ArrayList<>()); // When ProjectPlatforms result = mapper.toApiModel(externalPlatforms); @@ -107,11 +84,7 @@ void testToApiModel_WithNullDisabledPlatforms_MapsOtherFields() { @Test void testToApiModel_WithNullSections_MapsOtherFields() { // Given - Platforms externalPlatforms = - new Platforms(); - Map platformLinks = new HashMap<>(); - platformLinks.put("github", URI.create("https://github.com")); - externalPlatforms.setSections(null); + Platforms externalPlatforms = new Platforms(null); // When ProjectPlatforms result = mapper.toApiModel(externalPlatforms); @@ -121,34 +94,11 @@ void testToApiModel_WithNullSections_MapsOtherFields() { assertTrue(result.getSections().isEmpty()); } - @Test - void testToApiModel_WithEmptyCollections_ReturnsEmptyCollections() { - // Given - Platforms externalPlatforms = - new Platforms(); - externalPlatforms.setSections(new ArrayList<>()); - - // When - ProjectPlatforms result = mapper.toApiModel(externalPlatforms); - - // Then - assertNotNull(result); - assertNotNull(result.getSections()); - assertTrue(result.getSections().isEmpty()); - } - @Test void testToApiModel_WithSectionContainingNullLinks_HandlesGracefully() { // Given - Platforms externalPlatforms = - new Platforms(); - - PlatformSection section = - new PlatformSection(); - section.setSection("Development"); - section.setLinks(null); - - externalPlatforms.setSections(List.of(section)); + PlatformSection section = new PlatformSection("Development", null, null); + Platforms externalPlatforms = new Platforms(List.of(section)); // When ProjectPlatforms result = mapper.toApiModel(externalPlatforms); @@ -164,15 +114,9 @@ void testToApiModel_WithSectionContainingNullLinks_HandlesGracefully() { @Test void testToApiModel_WithSectionContainingEmptyLinks_ReturnsEmptyLinks() { // Given - Platforms externalPlatforms = - new Platforms(); + PlatformSection section = new PlatformSection("Development", null, new ArrayList<>()); - PlatformSection section = - new PlatformSection(); - section.setSection("Development"); - section.setLinks(new ArrayList<>()); - - externalPlatforms.setSections(List.of(section)); + Platforms externalPlatforms = new Platforms(List.of(section)); // When ProjectPlatforms result = mapper.toApiModel(externalPlatforms); @@ -189,14 +133,11 @@ void testToApiModel_WithSectionContainingEmptyLinks_ReturnsEmptyLinks() { @Test void testToApiModel_WithSectionsContainingNullSection_HandlesGracefully() { // Given - Platforms externalPlatforms = - new Platforms(); - List sections = new ArrayList<>(); sections.add(null); - externalPlatforms.setSections(sections); + Platforms externalPlatforms = new Platforms(sections); // When ProjectPlatforms result = mapper.toApiModel(externalPlatforms); @@ -211,19 +152,13 @@ void testToApiModel_WithSectionsContainingNullSection_HandlesGracefully() { @Test void testToApiModel_WithLinksContainingNullLink_HandlesGracefully() { // Given - Platforms externalPlatforms = - new Platforms(); + List links = new ArrayList<>(); + links.add(null); - PlatformSection section = - new PlatformSection(); - section.setSection("Development"); + PlatformSection section = new PlatformSection("Development", null, links); - List links = - new ArrayList<>(); - links.add(null); - section.setLinks(links); + Platforms externalPlatforms = new Platforms(List.of(section)); - externalPlatforms.setSections(List.of(section)); // When ProjectPlatforms result = mapper.toApiModel(externalPlatforms); @@ -259,17 +194,7 @@ void testToApiModel_WithMultipleSectionsAndLinks_MapsCorrectly() { // Helper method to create a complete external ProjectPlatforms object private Platforms createCompleteExternalProjectPlatforms() { - Platforms externalPlatforms = - new Platforms(); - - // Setup sections - List sections = new ArrayList<>(); - // First section with 2 links - PlatformSection section1 = - new PlatformSection(); - section1.setSection("Development"); - List links1 = new ArrayList<>(); PlatformSectionLink link1 = @@ -284,14 +209,9 @@ private Platforms createCompleteExternalProjectPlatforms() { link2.setUrl("https://jira.com/board"); links1.add(link2); - section1.setLinks(links1); - sections.add(section1); + PlatformSection section1 = new PlatformSection("Development", null, links1); // Second section with 1 link - PlatformSection section2 = - new PlatformSection(); - section2.setSection("CI/CD"); - List links2 = new ArrayList<>(); PlatformSectionLink link3 = @@ -300,12 +220,14 @@ private Platforms createCompleteExternalProjectPlatforms() { link3.setUrl("https://jenkins.com/job"); links2.add(link3); - section2.setLinks(links2); - sections.add(section2); + PlatformSection section2 = + new PlatformSection("CI/CD", null, links2); - externalPlatforms.setSections(sections); + // Setup sections + List sections = new ArrayList<>(List.of(section1, section2)); - return externalPlatforms; + return new Platforms(sections); } + } diff --git a/application.yaml b/application.yaml index 9ee9506..603ecc0 100644 --- a/application.yaml +++ b/application.yaml @@ -170,87 +170,3 @@ externalservices: projects-info-service: base-url: ${PROJECTS_INFO_SERVICE_BASE_URL:http://localhost:8081} - ssl: - verify-certificates: ${PROJECTS_INFO_SERVICE_SSL_VERIFY:true} - trust-store-path: ${PROJECTS_INFO_SERVICE_SSL_TRUSTSTORE_PATH:} - trust-store-password: ${PROJECTS_INFO_SERVICE_SSL_TRUSTSTORE_PASSWORD:} - trust-store-type: ${PROJECTS_INFO_SERVICE_SSL_TRUSTSTORE_TYPE:JKS} - azure: - access-token: ${PROJECTS_INFO_SERVICE_AZURE_ACCESS_TOKEN:tbc} - datahub: - group-id: ${PROJECTS_INFO_SERVICE_AZURE_DATA_HUB_GROUP_ID:tbc} - groups: - page-size: ${PROJECTS_INFO_SERVICE_AZURE_GROUPS_PAGE_SIZE:100} - testing-hub: - default: - projects: ${PROJECTS_INFO_SERVICE_TESTING_HUB_DEFAULT_PROJECTS:tbc} - api: - url: ${PROJECTS_INFO_SERVICE_TESTING_HUB_API_URL:tbc} - token: ${PROJECTS_INFO_SERVICE_TESTING_HUB_API_TOKEN:tbc} - page-size: ${PROJECTS_INFO_SERVICE_TESTING_HUB_API_PAGE_SIZE:100} - custom: - cache: - specs: - userGroups: - ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_TTL_SECONDS:60} - maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_MAXIMUM_SIZE:100} - userGroups-fallback: - ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_TTL_SECONDS:120} - maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_MAXIMUM_SIZE:100} - userEmail: - ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_TTL_SECONDS:60} - maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_MAXIMUM_SIZE:100} - userEmail-fallback: - ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_TTL_SECONDS:120} - maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_MAXIMUM_SIZE:100} - allEdpProjects: - ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_TTL_SECONDS:60} - maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_MAXIMUM_SIZE:100} - allEdpProjects-fallback: - ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_TTL_SECONDS:120} - maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_MAXIMUM_SIZE:100} - projectsInfoCache: - ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_TTL_SECONDS:60} - maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_MAXIMUM_SIZE:100} - projectsInfoCache-fallback: - ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_TTL_SECONDS:120} - maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_MAXIMUM_SIZE:100} - openshiftProjects: - ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_TTL_SECONDS:60} - maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_MAXIMUM_SIZE:100} - openshiftProjects-fallback: - ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_TTL_SECONDS:120} - maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_MAXIMUM_SIZE:100} - dataHubGroups: - ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_TTL_SECONDS:120} - maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_MAXIMUM_SIZE:100} - testingHubGroups: - ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_TTL_SECONDS:120} - maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_MAXIMUM_SIZE:100} - mock: - clusters: ${PROJECTS_INFO_SERVICE_MOCK_CLUSTERS:tbc} - projects: - default: ${PROJECTS_INFO_SERVICE_MOCK_DEFAULT_PROJECTS:tbc} - users: ${PROJECTS_INFO_SERVICE_MOCK_USER_PROJECTS:tbc} - openshift: - api: - clusters: - test: - url: ${PROJECTS_INFO_SERVICE_OPENSHIFT_TEST_URL:tbc} - token: ${PROJECTS_INFO_SERVICE_OPENSHIFT_TEST_TOKEN:tbc} - dev: - url: ${PROJECTS_INFO_SERVICE_OPENSHIFT_DEV_URL:tbc} - token: ${PROJECTS_INFO_SERVICE_OPENSHIFT_DEV_TOKEN:tbc} - project: - url: /apis/project.openshift.io/v1/projects - platforms: - bearer-token: ${PROJECTS_INFO_SERVICE_PLATFORMS_BEARER_TOKEN:tbc} - base-path: ${PROJECTS_INFO_SERVICE_PLATFORMS_BASE_PATH:tbc} - clusters: - test: ${PROJECTS_INFO_SERVICE_PLATFORMS_TEST_CLUSTER:tbc} - dev: ${PROJECTS_INFO_SERVICE_PLATFORMS_DEV_CLUSTER:tbc} - project: - filter: - project-roles-group-prefix: - # Properties to be used as lists cannot have leading or trailing blanks. - project-roles-group-suffixes: ROLE-A,ROLE-B diff --git a/external-service-projects-info-service/openapi/openapi-projects-info-service-v1.0.0.yaml b/external-service-projects-info-service/openapi/openapi-projects-info-service-v1.0.0.yaml new file mode 100644 index 0000000..251ab8f --- /dev/null +++ b/external-service-projects-info-service/openapi/openapi-projects-info-service-v1.0.0.yaml @@ -0,0 +1,264 @@ +openapi: 3.0.3 +info: + title: Projects info service REST API + version: '1.0.0' + description: > + The Projects Info Service API allows clients to collect project information for a predefined user. This user will be + Managed thanks to SSO token. + + **NOTES**: + - The OpenAPI specification file is also used to [generate](https://openapi-generator.tech/) REST client(s) and a server REST API. + - Clients and servers generated from the same OpenAPI specification version are guaranteed to be **compatible**. + contact: + name: OpenDevStack + url: https://www.opendevstack.org/ +servers: + - url: http://{baseurl}/v1 + variables: + baseurl: + default: localhost:8080 + description: Default address for a Projects Info Service's backend REST API instance. +security: + - bearerAuth: [] +tags: + - name: AzureGroups + description: Get azure groups operations. + - name: Projects + description: Get projects operations. +paths: + /azure/groups: + get: + tags: + - AzureGroups + summary: Get all azure groups associated to the user. + description: > + This endpoint receives an azure token, and returns all the groups associated to the user. + operationId: getAzureGroups + parameters: + - name: token + in: header + required: true + schema: + type: string + description: Azure token used to get the groups. + responses: + "200": + description: List of azure groups associated to the user. + content: + application/json: + schema: + type: array + items: + type: string + "401": + description: Invalid client token on the request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestErrorMessage' + "403": + description: Insufficient permissions for the client to access the resource. + content: + application/json: + schema: + $ref: '#/components/schemas/RestErrorMessage' + "500": + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestErrorMessage' + /projects: + get: + tags: + - Projects + summary: Get all the projects a user get access to. + description: > + Get all the projects a user get access to. For that, first of all it will get all the azure groups associated to the user, + and then it will get all the projects associated to those groups. + operationId: getProjects + parameters: + - name: token + in: header + required: true + schema: + type: string + description: Azure token used to get the groups. + responses: + "200": + description: List of projects the user has access to. + content: + application/json: + schema: + type: array + items: + type: string + "401": + description: Invalid client token on the request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestErrorMessage' + "403": + description: Insufficient permissions for the client to access the resource. + content: + application/json: + schema: + $ref: '#/components/schemas/RestErrorMessage' + "500": + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestErrorMessage' + /projects/{projectKey}/clusters: + get: + tags: + - Projects + summary: Get all project info and cluster for a given project key. + description: > + Get all project info and cluster for a given project key. + operationId: getProjectClusters + parameters: + - name: token + in: header + required: true + schema: + type: string + description: Azure token used to get the groups. + - name: projectKey + in: path + required: true + schema: + type: string + description: Project id to get the info and clusters. + responses: + "200": + description: Project info. + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectInfo' + + "401": + description: Invalid client token on the request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestErrorMessage' + "403": + description: Insufficient permissions for the client to access the resource. + content: + application/json: + schema: + $ref: '#/components/schemas/RestErrorMessage' + "500": + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestErrorMessage' + + /projects/{projectKey}/platforms: + get: + tags: + - Projects + summary: Get platform information for a given project key. + description: Returns disabled platforms and categorized platform links for the specified project. + operationId: getProjectPlatforms + parameters: + - name: projectKey + in: path + required: true + schema: + type: string + description: Project key to retrieve platform information. + responses: + '200': + description: Platform information for the project. + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectPlatforms' + "401": + description: Invalid client token on the request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestErrorMessage' + "403": + description: Insufficient permissions for the client to access the resource. + content: + application/json: + schema: + $ref: '#/components/schemas/RestErrorMessage' + +components: + securitySchemes: + bearerAuth: + type: http + description: > + Authorization via bearer token. + scheme: bearer + bearerFormat: JWT + schemas: + RestErrorMessage: + properties: + message: + type: string + required: + - message + ProjectInfo: + type: object + properties: + projectKey: + type: string + description: Project KEY. + clusters: + type: array + items: + type: string + description: List of clusters associated to the project. + required: + - projectKey + - projectName + - clusters + Link: + type: object + properties: + label: + type: string + url: + type: string + tooltip: + type: string + type: + type: string + abbreviation: + type: string + disabled: + type: boolean + required: + - label + - url + Section: + type: object + properties: + section: + type: string + tooltip: + type: string + links: + type: array + items: + $ref: '#/components/schemas/Link' + required: + - section + - links + ProjectPlatforms: + type: object + properties: + sections: + type: array + items: + $ref: '#/components/schemas/Section' \ No newline at end of file diff --git a/external-service-projects-info-service/pom.xml b/external-service-projects-info-service/pom.xml index 9eafead..cd427a6 100644 --- a/external-service-projects-info-service/pom.xml +++ b/external-service-projects-info-service/pom.xml @@ -78,6 +78,22 @@ 3.18.0 + + + javax.annotation + javax.annotation-api + 1.3.2 + provided + + + + + com.google.code.findbugs + jsr305 + 3.0.2 + provided + + org.springframework.boot @@ -102,6 +118,69 @@ + + org.openapitools + openapi-generator-maven-plugin + + + generate-api-project-users + + generate + + + java + ${project.basedir}/target/generated-sources/openapi + resttemplate + ${project.basedir}/openapi/openapi-projects-info-service-v1.0.0.yaml + org.opendevstack.apiservice.externalservice.projects_info_service.v1_0_0.client.api + org.opendevstack.apiservice.externalservice.projects_info_service.v1_0_0.client.model + org.opendevstack.apiservice.externalservice.projects_info_service.v1_0_0.client + true + true + true + false + false + false + false + default=defaultValue + + false + true + true + true + true + true + + + java8 + jackson + true + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source + generate-sources + + add-source + + + + ${project.basedir}/target/generated-sources/openapi/src/main/java + + + + + + org.springframework.boot spring-boot-maven-plugin diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/annotations/CacheableWithFallback.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/annotations/CacheableWithFallback.java deleted file mode 100644 index a18704f..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/annotations/CacheableWithFallback.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.annotations; - -import org.opendevstack.apiservice.externalservice.projectsinfoservice.config.CacheConfiguration; - -import java.lang.annotation.*; - -@Target({ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface CacheableWithFallback { - String primary(); - String fallback(); - String defaultValue() default ""; // SpEL or literal string - String cacheManager() default CacheConfiguration.CUSTOM_CACHE_MANAGER_NAME; -} diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/annotations/CacheableWithFallbackAspect.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/annotations/CacheableWithFallbackAspect.java deleted file mode 100644 index 13e0638..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/annotations/CacheableWithFallbackAspect.java +++ /dev/null @@ -1,173 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.annotations; - -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.reflect.MethodSignature; -import org.springframework.cache.Cache; -import org.springframework.cache.CacheManager; -import org.springframework.cache.interceptor.SimpleKeyGenerator; -import org.springframework.core.annotation.Order; -import org.springframework.expression.ExpressionParser; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.stereotype.Component; - -import java.lang.reflect.Method; - -@AllArgsConstructor -@Slf4j -@Aspect -@Component -@Order(1) -public class CacheableWithFallbackAspect { - - private static final String EMPTY_STRING = ""; - private final CacheManager cacheManager; - - @Around("@annotation(cacheableWithFallback)") - public Object cacheWithFallback(ProceedingJoinPoint pjp, CacheableWithFallback cacheableWithFallback) { - log.debug("CacheWithFallback. pjp: {}, annotation: {}", pjp, cacheableWithFallback); - - String primary = cacheableWithFallback.primary(); - String fallback = cacheableWithFallback.fallback(); - Object defaultValue = getDefaultValue(pjp, cacheableWithFallback); - - Object key = generateKey(pjp); - Cache primaryCache = cacheManager.getCache(primary); - Cache fallbackCache = cacheManager.getCache(fallback); - - Object result = null; - - if (primaryCache != null) { - result = extractValueFromPrimaryCache(primaryCache, key); - } - - if (result == null) { - Object pjpResult = extractValueFromRealMethodCall(pjp); - - if (pjpResult != null) { - result = pjpResult; - - updateCaches(primaryCache, fallbackCache, (String) key, result); - } else if (fallbackCache != null) { - result = extractValueFromFallbackCache(fallbackCache, key, defaultValue); - } else { - result = defaultValue; - - log.debug("No fallback cache configured. Returning default value: {}", defaultValue); - } - } - - log.debug("CacheableWithFallback result: {}", result); - return result; - } - - protected Object getDefaultValue(ProceedingJoinPoint pjp, CacheableWithFallback cacheableWithFallback) { - String defaultValueStr = cacheableWithFallback.defaultValue(); - Method method = ((MethodSignature) pjp.getSignature()).getMethod(); - Class returnType = method.getReturnType(); - - if (defaultValueStr != null && defaultValueStr.equals(EMPTY_STRING)) { - return EMPTY_STRING; - } else { - return resolveDefaultValue(defaultValueStr, returnType); - } - } - - - protected Object resolveDefaultValue(String defaultValue, Class returnType) { - if (defaultValue == null || defaultValue.isEmpty()) return null; - - ExpressionParser parser = new SpelExpressionParser(); - StandardEvaluationContext context = new StandardEvaluationContext(); - - Object result = parser.parseExpression(defaultValue).getValue(context); - - if (result != null && !returnType.isAssignableFrom(result.getClass())) { - throw new IllegalArgumentException("Default value type mismatch: expected " + - returnType.getName() + ", but got " + result.getClass().getName()); - } - - return result; - } - - protected Object generateKey(ProceedingJoinPoint pjp) { - log.debug("Generating key for method: {}", pjp.getSignature().getName()); - - Object generatedKey; - Object[] args = pjp.getArgs(); - - if (args == null || args.length == 0) { - generatedKey = pjp.getSignature().getName(); - } else { - generatedKey = SimpleKeyGenerator.generateKey(pjp.getArgs()); - } - - log.debug("Generated key: {}", generatedKey); - - return generatedKey; - } - - private Object extractValueFromPrimaryCache(Cache primaryCache, Object cacheKey) { - Object result = null; - - Cache.ValueWrapper primaryCachedValue = primaryCache.get(cacheKey); - if (primaryCachedValue != null) { - result = primaryCachedValue.get(); - - log.debug("Returning value from primary cache: {}, key: {}", primaryCache.getName(), cacheKey); - } - - return result; - } - - private Object extractValueFromFallbackCache(Cache fallbackCache, Object cacheKey, Object defaultValue) { - log.debug("There were an issue getting the real value. Trying to get value from fallback cache: {}, key: {}", fallbackCache, cacheKey); - - Object result; - - Cache.ValueWrapper fallbackCachedValue = fallbackCache.get(cacheKey); - if (fallbackCachedValue != null) { - result = fallbackCachedValue.get(); - - log.debug("Returning value from fallback cache: {}, key: {}", fallbackCache, cacheKey); - } else { - result = defaultValue; - - log.debug("No value in fallback cache. Returning default value: {}", defaultValue); - } - - return result; - } - - private Object extractValueFromRealMethodCall(ProceedingJoinPoint pjp) { - Object pjpResult = null; - - try { - log.debug("Calling real method: {}", pjp.getSignature().getName()); - - pjpResult = pjp.proceed(); // call the actual method - } catch (Throwable throwable) { - log.error("There were an error calling real method: {}", pjp.getSignature().getName(), throwable); - } - - return pjpResult; - } - - private void updateCaches(Cache primaryCache, Cache fallbackCache, String key, Object result) { - log.debug("Real method call generated a result. Updating caches: {}, {}, key: {}", primaryCache, fallbackCache, key); - - if (primaryCache != null) { - primaryCache.put(key, result); - } - - if (fallbackCache != null) { - fallbackCache.put(key, result); - } - - log.debug("Caches updated. Primary: {}, Fallback: {}, key: {}", primaryCache, fallbackCache, key); - } -} diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/client/AzureGraphClient.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/client/AzureGraphClient.java deleted file mode 100644 index fe1bde3..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/client/AzureGraphClient.java +++ /dev/null @@ -1,183 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.client; - -import org.opendevstack.apiservice.externalservice.projectsinfoservice.annotations.CacheableWithFallback; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.exception.InvalidContentProcessException; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.exception.InvalidTokenException; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.exception.UnableToReachAzureException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.http.*; -import org.springframework.stereotype.Service; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.RestTemplate; - -import java.io.IOException; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -@Slf4j -@Service -public class AzureGraphClient { - - private static final String USER_INFO_URL = "https://graph.microsoft.com/v1.0/me"; - private static final String MEMBER_OF_URL = "https://graph.microsoft.com/v1.0/me/memberOf"; - public static final String ERROR_WHILE_GETTING_USER_GROUPS = "Error while getting user groups"; - public static final String ERROR_WHILE_PROCESSING_SERVER_RESPONSE = "Error while processing server response"; - public static final String ERROR_WHILE_GETTING_APPLICATION_GROUPS = "Error while getting application groups"; - - private final RestTemplate restTemplate; - private final ObjectMapper mapper; - - @Value("${externalservices.projects-info-service.azure.groups.page-size}") - private Integer pageSize; - - @Value("${externalservices.projects-info-service.azure.access-token}") - private String azureAccessToken; - - @Value("${externalservices.projects-info-service.azure.datahub.group-id}") - private String dataHubGroupId; - - AzureGraphClient(RestTemplate restTemplate) { - this.restTemplate = restTemplate; - this.mapper = new ObjectMapper(); - } - - public Set getUserGroups(String userAccessToken) { - Set groupIds = new HashSet<>(); - String url = MEMBER_OF_URL + "?$top=" + pageSize; // e.g., "https://graph.microsoft.com/v1.0/me/memberOf?$top=100" - - HttpHeaders headers = new HttpHeaders(); - headers.setBearerAuth(userAccessToken); - headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); - HttpEntity entity = new HttpEntity<>(headers); - - try { - while (url != null) { - ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); - groupIds.addAll(processAzureResponse(response)); - - // Parse the nextLink if present - JsonNode root = new ObjectMapper().readTree(response.getBody()); - JsonNode nextLinkNode = root.get("@odata.nextLink"); - - if (nextLinkNode != null) { - log.debug("Next link found: {}", nextLinkNode.asText()); - - url = nextLinkNode != null ? nextLinkNode.asText() : null; - } else { - url = null; // No more pages - } - } - } catch (HttpClientErrorException | IOException e) { - log.error(ERROR_WHILE_GETTING_USER_GROUPS, e); - throw new InvalidTokenException(ERROR_WHILE_GETTING_USER_GROUPS, e); - } - - return groupIds; - } - - // TODO: Can not use short circuit at the moment, we will do it when integration is completed - @Cacheable("dataHubGroups") - public Set getDataHubGroups() { - return getApplicationGroups(azureAccessToken, dataHubGroupId); - } - - // TODO: Can not use short circuit at the moment, we will do it when integration is completed - @Cacheable("testingHubGroups") - public Set getTestingHubGroups() { - return getApplicationGroups(azureAccessToken, dataHubGroupId); - } - - protected Set getApplicationGroups(String accessToken, String appObjectId) { - Set groupNames = new HashSet<>(); - String url = "https://graph.microsoft.com/v1.0/servicePrincipals/" + appObjectId + "/appRoleAssignedTo?$top=" + pageSize; - - HttpHeaders headers = new HttpHeaders(); - headers.setBearerAuth(accessToken); - headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); - HttpEntity entity = new HttpEntity<>(headers); - - try { - while (url != null) { - ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); - groupNames.addAll(processAzureResponse(response)); - - JsonNode root = mapper.readTree(response.getBody()); - JsonNode nextLinkNode = root.get("@odata.nextLink"); - url = nextLinkNode != null ? nextLinkNode.asText() : null; - } - } catch (HttpClientErrorException e) { - log.error(ERROR_WHILE_GETTING_APPLICATION_GROUPS, e); - - if(e.getStatusCode() == HttpStatus.UNAUTHORIZED || e.getStatusCode() == HttpStatus.FORBIDDEN) { - throw new UnableToReachAzureException(ERROR_WHILE_GETTING_APPLICATION_GROUPS, e); - } else { - log.trace("There were an error when trying to get groups. We continue the process and consider platform as disabled, " + - "in order to not block application usage", e); - } - } catch (IOException e) { - log.error(ERROR_WHILE_PROCESSING_SERVER_RESPONSE, e); - } - - return groupNames; - } - - @CacheableWithFallback(primary = "userEmail", fallback = "userEmail-fallback") - public String getUserEmail(String accessToken) { - HttpHeaders headers = new HttpHeaders(); - headers.setBearerAuth(accessToken); - headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); - - HttpEntity entity = new HttpEntity<>(headers); - - try { - ResponseEntity response = restTemplate.exchange(USER_INFO_URL, HttpMethod.GET, entity, String.class); - JsonNode root = mapper.readTree(response.getBody()); - - // Try to get mail or userPrincipalName - if (root.has("mail") && !root.get("mail").isNull()) { - return root.get("mail").asText(); - } else if (root.has("userPrincipalName")) { - return root.get("userPrincipalName").asText(); - } else { - throw new InvalidContentProcessException("Email not found in user profile"); - } - - } catch (HttpClientErrorException e) { - log.error("Error while getting user email", e); - throw new InvalidTokenException("Error while getting user email", e); - } catch (Exception e) { - log.error("Error while processing user email response", e); - throw new InvalidContentProcessException("Error while processing user email response", e); - } - } - - - private Set processAzureResponse(ResponseEntity response) { - log.trace("Processing Azure response: {}", response.getBody()); - - Set groups = new HashSet<>(); - - try { - JsonNode root = mapper.readTree(response.getBody()); - JsonNode values = root.path("value"); - - for (JsonNode group : values) { - if (group.has("displayName")) { - groups.add(group.get("displayName").asText()); - } - } - } catch (Exception e) { - log.error(ERROR_WHILE_PROCESSING_SERVER_RESPONSE, e); - - throw new InvalidContentProcessException(ERROR_WHILE_PROCESSING_SERVER_RESPONSE, e); - } - - return groups; - } -} - diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/client/PlatformsYmlClient.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/client/PlatformsYmlClient.java deleted file mode 100644 index ac1ab89..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/client/PlatformsYmlClient.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.client; - -import org.apache.commons.lang3.tuple.Pair; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.config.PlatformsConfiguration; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.Platform; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.PlatformSectionService; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.PlatformsYml; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import lombok.AllArgsConstructor; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; - -import java.util.List; - -@Slf4j -@AllArgsConstructor -@Service -public class PlatformsYmlClient { - - private final RestTemplate restTemplate; - private final PlatformsConfiguration platformsConfiguration; - private final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory()); - - @SneakyThrows - public List fetchSectionsFromYaml(String url) { - - HttpHeaders headers = new HttpHeaders(); - headers.setBearerAuth(platformsConfiguration.getBearerToken()); // Adds "Authorization: Bearer " - HttpEntity entity = new HttpEntity<>(headers); - - log.debug("Fetching sections from YAML. url={}", url); - - ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); - String yamlContent = response.getBody(); - - PlatformsYml root = yamlMapper.readValue(yamlContent, PlatformsYml.class); - return root.getSections(); - } - - @SneakyThrows - public Pair> fetchPlatformsFromYaml(String url) { - - HttpHeaders headers = new HttpHeaders(); - headers.setBearerAuth(platformsConfiguration.getBearerToken()); // Adds "Authorization: Bearer " - HttpEntity entity = new HttpEntity<>(headers); - - log.debug("Fetching sections from YAML. url={}", url); - - ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); - String yamlContent = response.getBody(); - - PlatformsYml root = yamlMapper.readValue(yamlContent, PlatformsYml.class); - return Pair.of(root.getTitle(), root.getPlatforms()); - } -} - diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/client/TestingHubClient.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/client/TestingHubClient.java deleted file mode 100644 index 96012fa..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/client/TestingHubClient.java +++ /dev/null @@ -1,97 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.client; - -import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.TestingHubProject; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.*; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; - -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -@Slf4j -@Service -public class TestingHubClient { - - @Value("${externalservices.projects-info-service.testing-hub.api.url}") - private String url; - - @Value("${externalservices.projects-info-service.testing-hub.api.token}") - private String token; - - @Value("${externalservices.projects-info-service.testing-hub.api.page-size}") - private String pageSize; - - @Value("#{'${externalservices.projects-info-service.testing-hub.default.projects}'.split(',')}") - public List defaultProjects; - - private final RestTemplate restTemplate; - private final ObjectMapper jacksonMapper; - - public TestingHubClient(RestTemplate restTemplate, ObjectMapper jacksonMapper) { - this.restTemplate = restTemplate; - this.jacksonMapper = jacksonMapper; - } - - public Set getDefaultProjects() { - return defaultProjects.stream() - .map(String::trim) - .map(this::parseKeyAndId) - .map(keyAndId -> new TestingHubProject(keyAndId.getRight(), keyAndId.getLeft())) - .collect(Collectors.toSet()); - } - - // TODO: This method will be used when TestingHub notifies the changes on their API. - public Set getAllProjects() { - HttpHeaders headers = new HttpHeaders(); - headers.set("accept", "application/json"); - headers.set("X-Id-Token", "Bearer " + token); - - HttpEntity entity = new HttpEntity<>(headers); - - // Let's discuss later about pagination handling - var requestUrl = url + "?pageNumber=0&itemsPerPage=" + pageSize; - - ResponseEntity response = restTemplate.exchange( - requestUrl, - HttpMethod.GET, - entity, - String.class - ); - - if (response.getStatusCode() == HttpStatus.OK) { - String responseBody = response.getBody(); - - try { - return jacksonMapper.readValue(responseBody, new TypeReference<>() {}); - } catch (JsonProcessingException e) { - log.error("Failed to deserialize response", e); - - return Collections.emptySet(); - } - } else { - log.error("Failed to fetch projects from Testing Hub. Status code: {}", response.getStatusCode()); - - return Collections.emptySet(); - } - } - - private Pair parseKeyAndId(String keyAndId) { - var parts = keyAndId.split(":"); - - if (parts.length != 2) { - throw new IllegalArgumentException("Invalid project key and id format: " + keyAndId + - ". Expected format: :"); - } - - return new ImmutablePair<>(parts[0], parts[1]); - } -} diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/CacheConfiguration.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/CacheConfiguration.java deleted file mode 100644 index e6297a6..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/CacheConfiguration.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.config; - -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.RemovalListener; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.cache.Cache; -import org.springframework.cache.CacheManager; -import org.springframework.cache.annotation.EnableCaching; -import org.springframework.cache.caffeine.CaffeineCache; -import org.springframework.cache.support.SimpleCacheManager; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -@Configuration -@EnableCaching -@AllArgsConstructor -@Slf4j -public class CacheConfiguration { - - public static final String CUSTOM_CACHE_MANAGER_NAME = "customCacheManager"; - - private final CacheSpecPropertiesConfiguration cacheSpecProperties; - - @Bean(CUSTOM_CACHE_MANAGER_NAME) - public CacheManager customCacheManager() { - SimpleCacheManager manager = new SimpleCacheManager(); - - List caches = cacheSpecProperties.getSpecs().entrySet().stream() - .map(entry -> createCache(entry.getKey(), entry.getValue())) - .collect(Collectors.toList()); - - manager.setCaches(caches); - return manager; - } - - // Update this method in case we need to support different cache types - private Cache createCache(String name, CacheSpecPropertiesConfiguration.CacheSpec spec) { - RemovalListener listener = (key, value, cause) -> { - if (cause.wasEvicted()) { - log.debug("Eviction from cache '{}': key={}, cause={}", name, key, cause); - // Add custom logic here (e.g., notify, audit, etc.) - } else { - log.debug("Removing from cache '{}': key={}, cause={}", name, key, cause); - } - }; - - return new CaffeineCache( - name, - Caffeine.newBuilder() - .expireAfterWrite(spec.getTtl(), TimeUnit.SECONDS) - .maximumSize(spec.getMaxSize()) - .evictionListener(listener) - .removalListener(listener) - .build() - ); - } - -} diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/CacheSpecPropertiesConfiguration.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/CacheSpecPropertiesConfiguration.java deleted file mode 100644 index 640da53..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/CacheSpecPropertiesConfiguration.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.config; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -import java.util.Map; - -@Configuration -@Getter -@Setter -@ConfigurationProperties(prefix = "externalservices.projects-info-service.custom.cache") -public class CacheSpecPropertiesConfiguration { - private Map specs; - - @Getter - @Setter - public static class CacheSpec { - private long ttl; - private int maxSize; - } -} diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/MockConfiguration.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/MockConfiguration.java deleted file mode 100644 index fff0536..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/MockConfiguration.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.config; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Configuration; - -import java.util.List; - -@Configuration -@Getter -@Setter -public class MockConfiguration { - - @Value("#{'${externalservices.projects-info-service.mock.clusters}'.split(',')}") - private List clusters; - - @Value("#{'${externalservices.projects-info-service.mock.projects.default}'.split(',')}") - private List defaultProjects; - - @Value("${externalservices.projects-info-service.mock.projects.users}") - private String usersProjects; -} diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/OpenshiftClusterConfiguration.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/OpenshiftClusterConfiguration.java deleted file mode 100644 index 18d76ab..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/OpenshiftClusterConfiguration.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.config; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -import java.util.Map; - -@Getter -@Setter -@Configuration -@ConfigurationProperties(prefix = "externalservices.projects-info-service.openshift.api") -public class OpenshiftClusterConfiguration { - private Map> clusters; - -} \ No newline at end of file diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/PlatformsConfiguration.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/PlatformsConfiguration.java deleted file mode 100644 index cf85e06..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/PlatformsConfiguration.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.config; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -import java.util.Map; - -@Getter -@Setter -@Configuration -@ConfigurationProperties(prefix = "externalservices.projects-info-service.platforms") -public class PlatformsConfiguration { - private String basePath; - private String bearerToken; - private Map clusters; -} diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/ProjectFilterConfiguration.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/ProjectFilterConfiguration.java deleted file mode 100644 index 6098827..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/ProjectFilterConfiguration.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.config; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Configuration; - -import java.util.List; - -@Configuration -@Getter -@Setter -public class ProjectFilterConfiguration { - - @Value("${externalservices.projects-info-service.project.filter.project-roles-group-prefix}") - private String projectRolesGroupPrefix; - - @Value("#{'${externalservices.projects-info-service.project.filter.project-roles-group-suffixes}'.split(',')}") - private List projectRolesGroupSuffixes; - -} - diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/ProjectsInfoServiceConfig.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/ProjectsInfoServiceConfig.java index 06c29f0..6881360 100644 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/ProjectsInfoServiceConfig.java +++ b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/ProjectsInfoServiceConfig.java @@ -1,6 +1,10 @@ package org.opendevstack.apiservice.externalservice.projectsinfoservice.config; import lombok.extern.slf4j.Slf4j; +import org.opendevstack.apiservice.externalservice.projects_info_service.v1_0_0.client.ApiClient; +import org.opendevstack.apiservice.externalservice.projects_info_service.v1_0_0.client.api.ProjectsApi; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; @@ -29,6 +33,9 @@ @Slf4j public class ProjectsInfoServiceConfig { + @Value("${externalservices.projects-info-service.base-url:http://localhost:8080}") + private String baseUrl; + private final ProjectsInfoServiceSslProperties sslProperties; public ProjectsInfoServiceConfig(ProjectsInfoServiceSslProperties sslProperties) { @@ -51,28 +58,39 @@ public RestTemplate projectsInfoServiceRestTemplate(RestTemplateBuilder restTemp } } + @Bean + public ApiClient apiClient(RestTemplate restTemplate) { + return new ApiClient(restTemplate); + } + + @Qualifier("ProjectsInfoServiceApiClient") + @Bean + public ProjectsApi projectsApi(ApiClient apiClient) { + return new ProjectsApi(apiClient); + } + private RestTemplate createInsecureRestTemplate() { try { // Create a trust manager that accepts all certificates // WARNING: This is insecure and should only be used in development environments TrustManager[] trustAllCerts = new TrustManager[] { - new X509TrustManager() { - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; // Return empty array instead of null + new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; // Return empty array instead of null + } + public void checkClientTrusted(X509Certificate[] certs, String authType) { + // Intentionally empty - accepts all client certificates (insecure) + } + public void checkServerTrusted(X509Certificate[] certs, String authType) { + // Intentionally empty - accepts all server certificates (insecure) + } } - public void checkClientTrusted(X509Certificate[] certs, String authType) { - // Intentionally empty - accepts all client certificates (insecure) - } - public void checkServerTrusted(X509Certificate[] certs, String authType) { - // Intentionally empty - accepts all server certificates (insecure) - } - } }; // Install the all-trusting trust manager SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); - + // Create hostname verifier that accepts all hostnames (insecure) HostnameVerifier allHostsValid = (hostname, session) -> true; @@ -89,7 +107,7 @@ protected void prepareConnection(HttpURLConnection connection, String httpMethod }; return new RestTemplate(requestFactory); - + } catch (GeneralSecurityException e) { log.warn("Failed to create insecure RestTemplate, falling back to default: {}", e.getMessage()); return new RestTemplate(); @@ -101,12 +119,12 @@ private RestTemplate createSecureRestTemplate() { // If custom trust store is provided, configure it if (StringUtils.hasText(sslProperties.getTrustStorePath())) { log.info("Custom trust store specified: {} (custom trust store support can be added in future versions)", - sslProperties.getTrustStorePath()); + sslProperties.getTrustStorePath()); } - + // Return default RestTemplate with system SSL settings return new RestTemplate(); - + } catch (Exception e) { log.warn("Failed to create secure RestTemplate with custom trust store, using default: {}", e.getMessage()); return new RestTemplate(); diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/ProjectsInfoServiceSslProperties.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/ProjectsInfoServiceSslProperties.java index 7463b1e..4cef9c8 100644 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/ProjectsInfoServiceSslProperties.java +++ b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/ProjectsInfoServiceSslProperties.java @@ -7,24 +7,24 @@ */ @ConfigurationProperties(prefix = "externalservices.projects-info-service.ssl") public class ProjectsInfoServiceSslProperties { - + /** * Whether to verify SSL certificates when making external service calls. * Default is true for security. */ private boolean verifyCertificates = true; - + /** * Path to the trust store file for SSL certificate validation. * Optional - if not provided, uses system default trust store. */ private String trustStorePath; - + /** * Password for the trust store. */ private String trustStorePassword; - + /** * Type of the trust store (JKS, PKCS12, etc.). * Default is JKS. diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/Link.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/Link.java deleted file mode 100644 index e73455b..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/Link.java +++ /dev/null @@ -1,302 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.dto; - -import java.net.URI; -import java.util.Objects; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonCreator; -import org.openapitools.jackson.nullable.JsonNullable; -import java.time.OffsetDateTime; -import jakarta.validation.Valid; -import jakarta.validation.constraints.*; -import io.swagger.v3.oas.annotations.media.Schema; - -import java.util.*; -import jakarta.annotation.Generated; - -/** - * Link - */ - -@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", comments = "Generator version: 7.10.0") -public class Link { - - private String label; - - private String url; - - private String tooltip; - - private String type; - - private String abbreviation; - - private Boolean disabled; - - public Link() { - super(); - } - - /** - * Constructor with only required parameters - */ - public Link(String label, String url) { - this.label = label; - this.url = url; - } - - public Link label(String label) { - this.label = label; - return this; - } - - /** - * Get label - * @return label - */ - @NotNull - @Schema(name = "label", requiredMode = Schema.RequiredMode.REQUIRED) - @JsonProperty("label") - public String getLabel() { - return label; - } - - public void setLabel(String label) { - this.label = label; - } - - public Link url(String url) { - this.url = url; - return this; - } - - /** - * Get url - * @return url - */ - @NotNull - @Schema(name = "url", requiredMode = Schema.RequiredMode.REQUIRED) - @JsonProperty("url") - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public Link tooltip(String tooltip) { - this.tooltip = tooltip; - return this; - } - - /** - * Get tooltip - * @return tooltip - */ - - @Schema(name = "tooltip", requiredMode = Schema.RequiredMode.NOT_REQUIRED) - @JsonProperty("tooltip") - public String getTooltip() { - return tooltip; - } - - public void setTooltip(String tooltip) { - this.tooltip = tooltip; - } - - public Link type(String type) { - this.type = type; - return this; - } - - /** - * Get type - * @return type - */ - - @Schema(name = "type", requiredMode = Schema.RequiredMode.NOT_REQUIRED) - @JsonProperty("type") - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public Link abbreviation(String abbreviation) { - this.abbreviation = abbreviation; - return this; - } - - /** - * Get abbreviation - * @return abbreviation - */ - - @Schema(name = "abbreviation", requiredMode = Schema.RequiredMode.NOT_REQUIRED) - @JsonProperty("abbreviation") - public String getAbbreviation() { - return abbreviation; - } - - public void setAbbreviation(String abbreviation) { - this.abbreviation = abbreviation; - } - - public Link disabled(Boolean disabled) { - this.disabled = disabled; - return this; - } - - /** - * Get disabled - * @return disabled - */ - - @Schema(name = "disabled", requiredMode = Schema.RequiredMode.NOT_REQUIRED) - @JsonProperty("disabled") - public Boolean getDisabled() { - return disabled; - } - - public void setDisabled(Boolean disabled) { - this.disabled = disabled; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Link link = (Link) o; - return Objects.equals(this.label, link.label) && - Objects.equals(this.url, link.url) && - Objects.equals(this.tooltip, link.tooltip) && - Objects.equals(this.type, link.type) && - Objects.equals(this.abbreviation, link.abbreviation) && - Objects.equals(this.disabled, link.disabled); - } - - @Override - public int hashCode() { - return Objects.hash(label, url, tooltip, type, abbreviation, disabled); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("class Link {\n"); - sb.append(" label: ").append(toIndentedString(label)).append("\n"); - sb.append(" url: ").append(toIndentedString(url)).append("\n"); - sb.append(" tooltip: ").append(toIndentedString(tooltip)).append("\n"); - sb.append(" type: ").append(toIndentedString(type)).append("\n"); - sb.append(" abbreviation: ").append(toIndentedString(abbreviation)).append("\n"); - sb.append(" disabled: ").append(toIndentedString(disabled)).append("\n"); - sb.append("}"); - return sb.toString(); - } - - /** - * Convert the given object to string with each line indented by 4 spaces - * (except the first line). - */ - private String toIndentedString(Object o) { - if (o == null) { - return "null"; - } - return o.toString().replace("\n", "\n "); - } - - public static class Builder { - - private Link instance; - - public Builder() { - this(new Link()); - } - - protected Builder(Link instance) { - this.instance = instance; - } - - protected Builder copyOf(Link value) { - this.instance.setLabel(value.label); - this.instance.setUrl(value.url); - this.instance.setTooltip(value.tooltip); - this.instance.setType(value.type); - this.instance.setAbbreviation(value.abbreviation); - this.instance.setDisabled(value.disabled); - return this; - } - - public Link.Builder label(String label) { - this.instance.label(label); - return this; - } - - public Link.Builder url(String url) { - this.instance.url(url); - return this; - } - - public Link.Builder tooltip(String tooltip) { - this.instance.tooltip(tooltip); - return this; - } - - public Link.Builder type(String type) { - this.instance.type(type); - return this; - } - - public Link.Builder abbreviation(String abbreviation) { - this.instance.abbreviation(abbreviation); - return this; - } - - public Link.Builder disabled(Boolean disabled) { - this.instance.disabled(disabled); - return this; - } - - /** - * returns a built Link instance. - * - * The builder is not reusable (NullPointerException) - */ - public Link build() { - try { - return this.instance; - } finally { - // ensure that this.instance is not reused - this.instance = null; - } - } - - @Override - public String toString() { - return getClass() + "=(" + instance + ")"; - } - } - - /** - * Create a builder with no initialized field (except for the default values). - */ - public static Link.Builder builder() { - return new Link.Builder(); - } - - /** - * Create a builder with a shallow copy of this instance. - */ - public Link.Builder toBuilder() { - Link.Builder builder = new Link.Builder(); - return builder.copyOf(this); - } - -} - diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/ProjectInfo.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/ProjectInfo.java deleted file mode 100644 index 2723eb7..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/ProjectInfo.java +++ /dev/null @@ -1,188 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.dto; - -import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.annotation.Generated; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * ProjectInfo - */ - -@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", comments = "Generator version: 7.10.0") -public class ProjectInfo { - - private String projectKey; - - @Valid - private List clusters = new ArrayList<>(); - - public ProjectInfo() { - super(); - } - - /** - * Constructor with only required parameters - */ - public ProjectInfo(String projectKey, List clusters) { - this.projectKey = projectKey; - this.clusters = clusters; - } - - public ProjectInfo projectKey(String projectKey) { - this.projectKey = projectKey; - return this; - } - - /** - * Project KEY. - * @return projectKey - */ - @NotNull - @Schema(name = "projectKey", description = "Project KEY.", requiredMode = Schema.RequiredMode.REQUIRED) - @JsonProperty("projectKey") - public String getProjectKey() { - return projectKey; - } - - public void setProjectKey(String projectKey) { - this.projectKey = projectKey; - } - - public ProjectInfo clusters(List clusters) { - this.clusters = clusters; - return this; - } - - public ProjectInfo addClustersItem(String clustersItem) { - if (this.clusters == null) { - this.clusters = new ArrayList<>(); - } - this.clusters.add(clustersItem); - return this; - } - - /** - * List of clusters associated to the project. - * @return clusters - */ - @NotNull - @Schema(name = "clusters", description = "List of clusters associated to the project.", requiredMode = Schema.RequiredMode.REQUIRED) - @JsonProperty("clusters") - public List getClusters() { - return clusters; - } - - public void setClusters(List clusters) { - this.clusters = clusters; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ProjectInfo projectInfo = (ProjectInfo) o; - return Objects.equals(this.projectKey, projectInfo.projectKey) && - Objects.equals(this.clusters, projectInfo.clusters); - } - - @Override - public int hashCode() { - return Objects.hash(projectKey, clusters); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("class ProjectInfo {\n"); - sb.append(" projectKey: ").append(toIndentedString(projectKey)).append("\n"); - sb.append(" clusters: ").append(toIndentedString(clusters)).append("\n"); - sb.append("}"); - return sb.toString(); - } - - /** - * Convert the given object to string with each line indented by 4 spaces - * (except the first line). - */ - private String toIndentedString(Object o) { - if (o == null) { - return "null"; - } - return o.toString().replace("\n", "\n "); - } - - public static class Builder { - - private ProjectInfo instance; - - public Builder() { - this(new ProjectInfo()); - } - - protected Builder(ProjectInfo instance) { - this.instance = instance; - } - - protected Builder copyOf(ProjectInfo value) { - this.instance.setProjectKey(value.projectKey); - this.instance.setClusters(value.clusters); - return this; - } - - public Builder projectKey(String projectKey) { - this.instance.projectKey(projectKey); - return this; - } - - public Builder clusters(List clusters) { - this.instance.clusters(clusters); - return this; - } - - /** - * returns a built ProjectInfo instance. - * - * The builder is not reusable (NullPointerException) - */ - public ProjectInfo build() { - try { - return this.instance; - } finally { - // ensure that this.instance is not reused - this.instance = null; - } - } - - @Override - public String toString() { - return getClass() + "=(" + instance + ")"; - } - } - - /** - * Create a builder with no initialized field (except for the default values). - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Create a builder with a shallow copy of this instance. - */ - public Builder toBuilder() { - Builder builder = new Builder(); - return builder.copyOf(this); - } - -} - diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/ProjectPlatforms.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/ProjectPlatforms.java deleted file mode 100644 index 076e64a..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/ProjectPlatforms.java +++ /dev/null @@ -1,153 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.dto; - -import java.net.URI; -import java.util.Objects; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonCreator; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import org.openapitools.jackson.nullable.JsonNullable; -import java.time.OffsetDateTime; -import jakarta.validation.Valid; -import jakarta.validation.constraints.*; -import io.swagger.v3.oas.annotations.media.Schema; - - -import java.util.*; -import jakarta.annotation.Generated; - -/** - * ProjectPlatforms - */ - -@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", comments = "Generator version: 7.10.0") -public class ProjectPlatforms { - - @Valid - private List<@Valid Section> sections = new ArrayList<>(); - - public ProjectPlatforms sections(List<@Valid Section> sections) { - this.sections = sections; - return this; - } - - public ProjectPlatforms addSectionsItem(Section sectionsItem) { - if (this.sections == null) { - this.sections = new ArrayList<>(); - } - this.sections.add(sectionsItem); - return this; - } - - /** - * Get sections - * @return sections - */ - @Valid - @Schema(name = "sections", requiredMode = Schema.RequiredMode.NOT_REQUIRED) - @JsonProperty("sections") - public List<@Valid Section> getSections() { - return sections; - } - - public void setSections(List<@Valid Section> sections) { - this.sections = sections; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ProjectPlatforms projectPlatforms = (ProjectPlatforms) o; - return Objects.equals(this.sections, projectPlatforms.sections); - } - - @Override - public int hashCode() { - return Objects.hash(sections); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("class ProjectPlatforms {\n"); - sb.append(" sections: ").append(toIndentedString(sections)).append("\n"); - sb.append("}"); - return sb.toString(); - } - - /** - * Convert the given object to string with each line indented by 4 spaces - * (except the first line). - */ - private String toIndentedString(Object o) { - if (o == null) { - return "null"; - } - return o.toString().replace("\n", "\n "); - } - - public static class Builder { - - private ProjectPlatforms instance; - - public Builder() { - this(new ProjectPlatforms()); - } - - protected Builder(ProjectPlatforms instance) { - this.instance = instance; - } - - protected Builder copyOf(ProjectPlatforms value) { - this.instance.setSections(value.sections); - return this; - } - - public ProjectPlatforms.Builder sections(List<@Valid Section> sections) { - this.instance.sections(sections); - return this; - } - - /** - * returns a built ProjectPlatforms instance. - * - * The builder is not reusable (NullPointerException) - */ - public ProjectPlatforms build() { - try { - return this.instance; - } finally { - // ensure that this.instance is not reused - this.instance = null; - } - } - - @Override - public String toString() { - return getClass() + "=(" + instance + ")"; - } - } - - /** - * Create a builder with no initialized field (except for the default values). - */ - public static ProjectPlatforms.Builder builder() { - return new ProjectPlatforms.Builder(); - } - - /** - * Create a builder with a shallow copy of this instance. - */ - public ProjectPlatforms.Builder toBuilder() { - ProjectPlatforms.Builder builder = new ProjectPlatforms.Builder(); - return builder.copyOf(this); - } - -} - diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/Section.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/Section.java deleted file mode 100644 index d0a3b09..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/Section.java +++ /dev/null @@ -1,225 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.dto; - -import java.net.URI; -import java.util.Objects; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonCreator; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import org.openapitools.jackson.nullable.JsonNullable; -import java.time.OffsetDateTime; -import jakarta.validation.Valid; -import jakarta.validation.constraints.*; -import io.swagger.v3.oas.annotations.media.Schema; - - -import java.util.*; -import jakarta.annotation.Generated; - -/** - * Section - */ - -@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", comments = "Generator version: 7.10.0") -public class Section { - - private String section; - - private String tooltip; - - @Valid - private List<@Valid Link> links = new ArrayList<>(); - - public Section() { - super(); - } - - /** - * Constructor with only required parameters - */ - public Section(String section, List<@Valid Link> links) { - this.section = section; - this.links = links; - } - - public Section section(String section) { - this.section = section; - return this; - } - - /** - * Get section - * @return section - */ - @NotNull - @Schema(name = "section", requiredMode = Schema.RequiredMode.REQUIRED) - @JsonProperty("section") - public String getSection() { - return section; - } - - public void setSection(String section) { - this.section = section; - } - - public Section tooltip(String tooltip) { - this.tooltip = tooltip; - return this; - } - - /** - * Get tooltip - * @return tooltip - */ - - @Schema(name = "tooltip", requiredMode = Schema.RequiredMode.NOT_REQUIRED) - @JsonProperty("tooltip") - public String getTooltip() { - return tooltip; - } - - public void setTooltip(String tooltip) { - this.tooltip = tooltip; - } - - public Section links(List<@Valid Link> links) { - this.links = links; - return this; - } - - public Section addLinksItem(Link linksItem) { - if (this.links == null) { - this.links = new ArrayList<>(); - } - this.links.add(linksItem); - return this; - } - - /** - * Get links - * @return links - */ - @NotNull @Valid - @Schema(name = "links", requiredMode = Schema.RequiredMode.REQUIRED) - @JsonProperty("links") - public List<@Valid Link> getLinks() { - return links; - } - - public void setLinks(List<@Valid Link> links) { - this.links = links; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Section section = (Section) o; - return Objects.equals(this.section, section.section) && - Objects.equals(this.tooltip, section.tooltip) && - Objects.equals(this.links, section.links); - } - - @Override - public int hashCode() { - return Objects.hash(section, tooltip, links); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("class Section {\n"); - sb.append(" section: ").append(toIndentedString(section)).append("\n"); - sb.append(" tooltip: ").append(toIndentedString(tooltip)).append("\n"); - sb.append(" links: ").append(toIndentedString(links)).append("\n"); - sb.append("}"); - return sb.toString(); - } - - /** - * Convert the given object to string with each line indented by 4 spaces - * (except the first line). - */ - private String toIndentedString(Object o) { - if (o == null) { - return "null"; - } - return o.toString().replace("\n", "\n "); - } - - public static class Builder { - - private Section instance; - - public Builder() { - this(new Section()); - } - - protected Builder(Section instance) { - this.instance = instance; - } - - protected Builder copyOf(Section value) { - this.instance.setSection(value.section); - this.instance.setTooltip(value.tooltip); - this.instance.setLinks(value.links); - return this; - } - - public Section.Builder section(String section) { - this.instance.section(section); - return this; - } - - public Section.Builder tooltip(String tooltip) { - this.instance.tooltip(tooltip); - return this; - } - - public Section.Builder links(List<@Valid Link> links) { - this.instance.links(links); - return this; - } - - /** - * returns a built Section instance. - * - * The builder is not reusable (NullPointerException) - */ - public Section build() { - try { - return this.instance; - } finally { - // ensure that this.instance is not reused - this.instance = null; - } - } - - @Override - public String toString() { - return getClass() + "=(" + instance + ")"; - } - } - - /** - * Create a builder with no initialized field (except for the default values). - */ - public static Section.Builder builder() { - return new Section.Builder(); - } - - /** - * Create a builder with a shallow copy of this instance. - */ - public Section.Builder toBuilder() { - Section.Builder builder = new Section.Builder(); - return builder.copyOf(this); - } - -} - diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/exception/InvalidConfigurationException.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/exception/InvalidConfigurationException.java deleted file mode 100644 index 8e09fcd..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/exception/InvalidConfigurationException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.exception; - -public class InvalidConfigurationException extends RuntimeException { - public InvalidConfigurationException(String message) { - super(message); - } -} diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/exception/InvalidContentProcessException.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/exception/InvalidContentProcessException.java deleted file mode 100644 index b914a3c..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/exception/InvalidContentProcessException.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.exception; - -public class InvalidContentProcessException extends RuntimeException { - public InvalidContentProcessException(String message) { - super(message); - } - - public InvalidContentProcessException(String message, Exception cause) { - super(message, cause); - } -} diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/exception/InvalidTokenException.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/exception/InvalidTokenException.java deleted file mode 100644 index c58d8c1..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/exception/InvalidTokenException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.exception; - -public class InvalidTokenException extends RuntimeException { - public InvalidTokenException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/exception/UnableToReachAzureException.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/exception/UnableToReachAzureException.java deleted file mode 100644 index 0225172..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/exception/UnableToReachAzureException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.exception; - -public class UnableToReachAzureException extends RuntimeException { - public UnableToReachAzureException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/facade/ProjectsFacade.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/facade/ProjectsFacade.java deleted file mode 100644 index 5b83be6..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/facade/ProjectsFacade.java +++ /dev/null @@ -1,106 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.facade; - -import lombok.extern.slf4j.Slf4j; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.client.AzureGraphClient; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.dto.Link; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.dto.ProjectPlatforms; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.dto.Section; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.Platform; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.PlatformsWithTitle; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.service.MockProjectsService; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.service.OpenShiftProjectService; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.service.PlatformService; -import org.springframework.stereotype.Component; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -@Slf4j -@Component -public class ProjectsFacade { - - private final AzureGraphClient azureGraphClient; - - private final OpenShiftProjectService openShiftProjectService; - - private final MockProjectsService mockProjectsService; - - private final PlatformService platformService; - - private Map clusterMapper; - - public ProjectsFacade(AzureGraphClient azureGraphClient, - OpenShiftProjectService openShiftProjectService, - MockProjectsService mockProjectsService, - PlatformService platformService) { - this.azureGraphClient = azureGraphClient; - this.openShiftProjectService = openShiftProjectService; - this.mockProjectsService = mockProjectsService; - this.platformService = platformService; - } - - public ProjectPlatforms getProjectPlatforms(String projectKey) { - var allEdpProjectsInfo = openShiftProjectService.fetchProjects(); - var mockProjectsAndClusters = mockProjectsService.getDefaultProjectsAndClusters(); - - var edProjectInfo = allEdpProjectsInfo.stream() - .filter(p -> p.getProject().equals(projectKey)) - .findFirst(); - - var mockClusters = mockProjectsAndClusters.entrySet().stream() - .filter(e -> e.getValue().getProjectKey().equals(projectKey)) - .flatMap(e -> e.getValue().getClusters().stream()) - .toList(); - - // If EDP project exists, add its clusters to the front of the list, so we prioritize them - var mergedClusters = edProjectInfo.map(projectInfo -> { - List clusters = new ArrayList<>(); - - clusters.add(projectInfo.getCluster()); - - clusters.addAll(mockClusters); - - return List.copyOf(clusters); // We always prefer immutable lists - }).orElse(mockClusters); - - if (mergedClusters.isEmpty()) { - log.debug("Project not found: {}", projectKey); - - return null; - } else { - log.debug("Project found: {}, returning ProjectPlatforms for clusters: {}.", projectKey, mergedClusters); - - List
sections = new ArrayList<>(platformService.getSections(projectKey, mergedClusters.getFirst())); - var disabledPlatforms = platformService.getDisabledPlatforms(projectKey); - var platformsWithTitle = platformService.getPlatforms(projectKey, mergedClusters.getFirst()); - - var firstSection = componseFirstSection(platformsWithTitle, disabledPlatforms); - - sections.addFirst(firstSection); - - return ProjectPlatforms.builder() - .sections(sections) - .build(); - } - } - - private Section componseFirstSection(PlatformsWithTitle platformsWithTitle, List disabledPlatforms) { - var links = platformsWithTitle.getPlatforms().entrySet().stream() - .map(entry -> Link.builder() - .label(entry.getValue().getLabel()) - .url(entry.getValue().getUrl()) - .type("platform") - .disabled(disabledPlatforms.contains(entry.getKey())) - .abbreviation(entry.getValue().getAbbreviation()) - .build() - ) - .toList(); - - return Section.builder() - .section(platformsWithTitle.getTitle()) - .links(links) - .build(); - } - -} diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/Metadata.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/Metadata.java deleted file mode 100644 index b8a967e..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/Metadata.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Getter; -import lombok.Setter; - -@Setter -@Getter -@JsonIgnoreProperties(ignoreUnknown = true) -public class Metadata { - private String name; -} \ No newline at end of file diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/OpenshiftProjectCluster.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/OpenshiftProjectCluster.java deleted file mode 100644 index 7aa8c06..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/OpenshiftProjectCluster.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.model; - -import lombok.*; - -@Getter -@Setter -@AllArgsConstructor -@Builder -@EqualsAndHashCode -@ToString -public class OpenshiftProjectCluster { - String project; - String cluster; -} diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/Platform.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/Platform.java deleted file mode 100644 index 7f8bc0e..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/Platform.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.model; - -import lombok.*; - -@NoArgsConstructor -@AllArgsConstructor -@Getter -@Setter -@Builder -public class Platform { - private String id; - private String label; - private String url; - private String abbreviation; -} \ No newline at end of file diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformLink.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformLink.java deleted file mode 100644 index 1d34a87..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformLink.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.model; - -import lombok.*; - -@NoArgsConstructor -@AllArgsConstructor -@Builder -@Getter -@Setter -public class PlatformLink { - private String label; - private String url; - private String type; - private String tooltip; -} \ No newline at end of file diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformSection.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformSection.java index 948277e..6b00f57 100644 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformSection.java +++ b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformSection.java @@ -1,59 +1,13 @@ package org.opendevstack.apiservice.externalservice.projectsinfoservice.model; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import lombok.Data; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.dto.Section; /** * Represents a section of links from a specific platform associated to a project. */ -@Data -public class PlatformSection { - - private String section; - private String tooltip; - private List links; - - public PlatformSection() { - // default constructor - } - - public PlatformSection(Section section) { - this.section = section.getSection(); - this.tooltip = section.getTooltip(); - this.links = section.getLinks().stream() - .map(PlatformSectionLink::new) - .toList(); - } - - /** - * Creates a ProjectPlatformSection from a raw map. - * - * @param rawSection the raw map containing section data - * @return a new ProjectPlatformSection instance - */ - public static PlatformSection fromMap(Map rawSection) { - PlatformSection section = new PlatformSection(); - - if (rawSection.containsKey("section")) { - section.setSection((String) rawSection.get("section")); - } - - if (rawSection.containsKey("tooltip")) { - section.setTooltip((String) rawSection.get("tooltip")); - } - - if (rawSection.containsKey("links")) { - @SuppressWarnings("unchecked") - List> rawLinks = (List>) rawSection.get("links"); - List links = rawLinks.stream() - .map(PlatformSectionLink::fromMap) - .collect(Collectors.toList()); - section.setLinks(links); - } - - return section; - } +public record PlatformSection ( + String section, + String tooltip, + List links +){ } diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformSectionLink.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformSectionLink.java index 90ea31e..c5b9304 100644 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformSectionLink.java +++ b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformSectionLink.java @@ -1,7 +1,8 @@ package org.opendevstack.apiservice.externalservice.projectsinfoservice.model; import lombok.Data; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.dto.Link; +import org.opendevstack.apiservice.externalservice.projects_info_service.v1_0_0.client.model.Link; + import java.util.Map; diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformSectionService.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformSectionService.java deleted file mode 100644 index b14b2c7..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformSectionService.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.model; - -import lombok.*; - -import java.util.List; - -@NoArgsConstructor -@AllArgsConstructor -@Builder -@Getter -@Setter -public class PlatformSectionService { - private String section; - private String tooltip; - private List links; -} \ No newline at end of file diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/Platforms.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/Platforms.java index ce5956d..8254026 100644 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/Platforms.java +++ b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/Platforms.java @@ -1,38 +1,9 @@ package org.opendevstack.apiservice.externalservice.projectsinfoservice.model; -import java.net.URI; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import lombok.Data; /** * Represents the platforms information associated to a project. */ -@Data -public class Platforms { - - private List sections; - - /** - * Creates a ProjectPlatforms from a raw map response. - * - * @param responseBody the raw map containing platforms data - * @return a new ProjectPlatforms instance - */ - public static Platforms fromMap(Map responseBody) { - Platforms platforms = new Platforms(); - - // Map sections - if (responseBody.containsKey("sections")) { - @SuppressWarnings("unchecked") - List> rawSections = (List>) responseBody.get("sections"); - List sections = rawSections.stream() - .map(PlatformSection::fromMap) - .collect(Collectors.toList()); - platforms.setSections(sections); - } - - return platforms; - } +public record Platforms(List sections) { } diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformsWithTitle.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformsWithTitle.java deleted file mode 100644 index ee2cd0f..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformsWithTitle.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.model; - -import lombok.*; - -import java.util.Map; - -@NoArgsConstructor -@AllArgsConstructor -@Getter -@Setter -@Builder -public class PlatformsWithTitle { - private String title; - private Map platforms; -} diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformsYml.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformsYml.java deleted file mode 100644 index 7ab4836..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformsYml.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.model; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.util.List; - - -@NoArgsConstructor -@AllArgsConstructor -@Getter -@Setter -public class PlatformsYml { - private String title; - private List platforms; - private List sections; -} \ No newline at end of file diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/Project.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/Project.java deleted file mode 100644 index e86ef3b..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/Project.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Getter; -import lombok.Setter; - -@Setter -@Getter -@JsonIgnoreProperties(ignoreUnknown = true) -public class Project { - private Metadata metadata; -} \ No newline at end of file diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/ProjectList.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/ProjectList.java deleted file mode 100644 index f34e3c3..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/ProjectList.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Getter; -import lombok.Setter; - -import java.util.List; - -@Setter -@Getter -@JsonIgnoreProperties(ignoreUnknown = true) -public class ProjectList { - private List items; -} \ No newline at end of file diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/TestingHubProject.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/TestingHubProject.java deleted file mode 100644 index 6bb3411..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/TestingHubProject.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.model; - -import lombok.*; - -@NoArgsConstructor -@AllArgsConstructor -@Getter -@Setter -@EqualsAndHashCode -@ToString -public class TestingHubProject { - private String id; - private String name; -} diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/MockProjectsService.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/MockProjectsService.java deleted file mode 100644 index 5a4ba01..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/MockProjectsService.java +++ /dev/null @@ -1,119 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.service; - -import org.opendevstack.apiservice.externalservice.projectsinfoservice.config.MockConfiguration; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.dto.ProjectInfo; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.exception.InvalidContentProcessException; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -@Slf4j -@AllArgsConstructor -@Service -public class MockProjectsService { - - private final MockConfiguration mockConfiguration; - - public Map getProjectsAndClusters(String userEmail) { - Map defaultProjects = getDefaultProjectsAndClusters(); - Map userProjects = getUserProjectsAndClusters(userEmail); - - return merge(defaultProjects, userProjects); - } - - public Map getDefaultProjectsAndClusters() { - return mockConfiguration.getDefaultProjects().stream() - .map(String::trim) - .map(this::extractProjectAndCluster) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - } - - private Map getUserProjectsAndClusters(String userEmail) { - return getUserProjects(userEmail).stream() - .map(this::extractProjectAndCluster) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - } - - private Set getUserProjects(String userEmail) { - var projectsByUsers = mockConfiguration.getUsersProjects().replace("{", "") - .replace("}", "") - .split(";"); - - var userConfigurations = Stream.of(projectsByUsers) - .map(String::trim) - .filter(s -> s.startsWith(userEmail)) - .map(s -> s.replace(userEmail + ":", "").trim()) - .toList(); - - return userConfigurations.stream() - .map(this::getSingleUserProjects) - .flatMap(Set::stream) - .collect(Collectors.toSet()); - } - - private Set getSingleUserProjects(String userProjects) { - if (userProjects.equals("[]")) { - return Collections.emptySet(); - } else { - if (!userProjects.startsWith("[") || !userProjects.endsWith("]")) { - log.error("User projects string is not well formatted: {}", userProjects); - - throw new InvalidContentProcessException("User projects string is not well formatted: " + userProjects); - } - - var projects = userProjects.substring(userProjects.indexOf("[") + 1, userProjects.indexOf("]")) - .split(","); - - return Stream.of(projects) - .map(String::trim) - .map(this::extractProjectAndCluster) - .map(Map.Entry::getKey) - .collect(Collectors.toSet()); - } - } - - private Map.Entry extractProjectAndCluster(String projectWithCluster) { - if (projectWithCluster.contains(":")) { - var parts = projectWithCluster.split(":"); - - var key = parts[0].trim(); - var projectInfo = new ProjectInfo(key, List.of(parts[1].trim())); - - return Map.entry(key, projectInfo); - } else { - var key = projectWithCluster.trim(); - var projectInfo = new ProjectInfo(key, mockConfiguration.getClusters().stream() - .toList()); - - return Map.entry(key, projectInfo); - } - } - - private Map merge(Map map1, Map map2) { - Set allKeysSet = Stream.concat(map1.keySet().stream(), map2.keySet().stream()) - .collect(Collectors.toSet()); - - Map result = new HashMap<>(); - - for (String key : allKeysSet) { - ProjectInfo projectInfo1 = map1.getOrDefault(key, new ProjectInfo(key, Collections.emptyList())); - ProjectInfo projectInfo2 = map2.getOrDefault(key, new ProjectInfo(key, Collections.emptyList())); - - List mergedClusters = Stream.concat( - projectInfo1.getClusters().stream(), - projectInfo2.getClusters().stream() - ) - .map(String::trim) - .distinct() - .toList(); - - result.put(key, new ProjectInfo(key, mergedClusters)); - } - - return result; - } -} diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/OpenShiftProjectService.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/OpenShiftProjectService.java deleted file mode 100644 index ccea0a7..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/OpenShiftProjectService.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.service; - -import org.opendevstack.apiservice.externalservice.projectsinfoservice.config.OpenshiftClusterConfiguration; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.annotations.CacheableWithFallback; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.OpenshiftProjectCluster; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.ProjectList; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.*; -import org.springframework.stereotype.Service; -import org.springframework.web.client.*; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -@Slf4j -@Service -public class OpenShiftProjectService { - @Value("${externalservices.projects-info-service.openshift.api.project.url}") - private String projectApiUrl; - - private final RestTemplate restTemplate; - private final OpenshiftClusterConfiguration openshiftClusterConfig; - - public OpenShiftProjectService(RestTemplate restTemplate, OpenshiftClusterConfiguration openshiftClusterConfig) { - this.restTemplate = restTemplate; - this.openshiftClusterConfig = openshiftClusterConfig; - } - - @CacheableWithFallback(primary = "openshiftProjects", fallback = "openshiftProjects-fallback", defaultValue = "T(java.util.Collections).emptyList()") - public List fetchProjects() { - final List result = new ArrayList<>(); - - openshiftClusterConfig.getClusters().forEach((cluster, clusterValues) -> { - log.debug("Fetching projects for cluster: {}", cluster); - - final HttpHeaders headers = new HttpHeaders(); - headers.set("Authorization", "Bearer " + clusterValues.get("token")); - headers.setAccept(List.of(MediaType.APPLICATION_JSON)); - - final String url = clusterValues.get("url") + projectApiUrl; - final HttpEntity entity = new HttpEntity<>(headers); - - log.debug("Setting headers to request: {} for url {}", headers, url); - - try { - ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, ProjectList.class); - ProjectList body = response.getBody(); - - if (body != null && body.getItems() != null) { - log.debug("Found {} projects for cluster {}:", body.getItems().size(), cluster); - log.debug("Projects: \n {}", body.getItems().stream() - .map(project -> project.getMetadata().getName()) - .collect(Collectors.joining(", "))); - - List clusterProjects = body.getItems().stream() - .filter(project -> project.getMetadata() != null) - .map(project -> project.getMetadata().getName()) - .filter(projectName -> projectName.endsWith("-cd")) - .map(projectName -> projectName.replace("-cd", "")) - .map(String::toUpperCase) - .map(projectName -> OpenshiftProjectCluster.builder() - .project(projectName) - .cluster(cluster) - .build()) - .toList(); - - result.addAll(clusterProjects); - } - } catch (HttpClientErrorException | HttpServerErrorException e) { - log.error("HTTP error while fetching projects for cluster {}: {} - {}", cluster, e.getStatusCode(), e.getMessage()); - } catch (ResourceAccessException e) { - log.error("Resource access error for cluster {}: {}", cluster, e.getMessage()); - } catch (RestClientException e) { - log.error("Unexpected error while fetching projects for cluster {}: {}", cluster, e.getMessage()); - } - }); - - return result; - } -} diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/PlatformService.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/PlatformService.java deleted file mode 100644 index 818518e..0000000 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/PlatformService.java +++ /dev/null @@ -1,243 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.service; - -import org.opendevstack.apiservice.externalservice.projectsinfoservice.config.PlatformsConfiguration; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.config.ProjectFilterConfiguration; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.client.AzureGraphClient; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.client.PlatformsYmlClient; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.client.TestingHubClient; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.dto.Link; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.dto.Section; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.exception.InvalidConfigurationException; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.exception.UnableToReachAzureException; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.*; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import java.util.*; -import java.util.function.Function; -import java.util.stream.Collectors; - -@Slf4j -@AllArgsConstructor -@Service -public class PlatformService { - - private static final String PROJECT_KEY_TOKEN = "${projectKey}"; - private static final String LOWER_PROJECT_KEY_TOKEN = "${project_key}"; - private static final String UPPER_PROJECT_KEY_TOKEN = "${PROJECT_KEY}"; - private static final String TESTING_HUB_PROJECT_TOKEN = "${testingHubProject}"; - - PlatformsConfiguration platformsConfiguration; - ProjectFilterConfiguration projectFilterConfiguration; - PlatformsYmlClient platformsYmlClient; - AzureGraphClient azureGraphClient; - TestingHubClient testingHubClient; - - public PlatformsWithTitle getPlatforms(String projectKey, String cluster) { - log.debug("Getting platform links for project {} and cluster {}.", projectKey, cluster); - - if (platformsConfiguration.getClusters().containsKey(cluster)) { - var clusterConfigurationPath = platformsConfiguration.getClusters().get(cluster); - var url = platformsConfiguration.getBasePath() + clusterConfigurationPath; - - var titleAndPlatforms = platformsYmlClient.fetchPlatformsFromYaml(url); - var platforms = titleAndPlatforms.getValue(); - - platforms.forEach(platform -> { - var resolvedUrl = Optional.ofNullable(resolvePlatformUrl(platform.getUrl(), projectKey)).orElse(""); - - platform.setUrl(resolvedUrl); - }); - - var platformsMap = platforms.stream() - .collect(Collectors.toMap( - Platform::getId, - Function.identity(), - (existing, replacement) -> existing, // merge function - LinkedHashMap::new // supplier to preserve order - )); - - return PlatformsWithTitle.builder() - .title(titleAndPlatforms.getKey()) - .platforms(platformsMap) - .build(); - } else { - String errorMessage = "Cluster " + cluster + " is not configured. All valid clusters: " + - platformsConfiguration.getClusters().keySet(); - - throw new InvalidConfigurationException(errorMessage); - } - } - - private String resolvePlatformUrl(String urlTemplate, String projectKey) { - if (urlTemplate.contains(TESTING_HUB_PROJECT_TOKEN)) { - // TODO: Change to getAllProjects TestingHub notifies the changes on their API. - Set testingHubProjects = testingHubClient.getDefaultProjects(); - String testingHubValue = testingHubProjects.stream() - .filter(project -> project.getName().equals(projectKey)) - .map(TestingHubProject::getId) - .findFirst() - .orElse(null); - - // If the project is not found in TestingHub, we cannot resolve the URL so we return null - if (testingHubValue == null) { - return null; - } - - urlTemplate = urlTemplate.replace(TESTING_HUB_PROJECT_TOKEN, testingHubValue); - } - - return replaceTokens(urlTemplate, Map.of( - PROJECT_KEY_TOKEN, projectKey, - UPPER_PROJECT_KEY_TOKEN, projectKey.toUpperCase(), - LOWER_PROJECT_KEY_TOKEN, projectKey.toLowerCase() - )); - } - - public List getDisabledPlatforms(String projectKey) { - List disabledPlatforms = new ArrayList<>(); - - disabledPlatforms.addAll(addDisabledPlatformIfDataHubIsDisabled(projectKey)); - disabledPlatforms.addAll(addDisabledPlatformIfTestingHubIsDisabled(projectKey)); - - return disabledPlatforms; - } - - private List addDisabledPlatformIfDataHubIsDisabled(String projectKey) { - List disabledPlatforms = new ArrayList<>(); - - try { - var dataHubGroups = azureGraphClient.getDataHubGroups(); - - var isDataHubDisabled = checkIfProjectIsEnabledForGroups(projectKey, dataHubGroups); - - if (!isDataHubDisabled) { - disabledPlatforms.add("datahub"); - } - } catch (UnableToReachAzureException e) { - log.error("Unable to reach Azure to get DataHub groups", e); - - // As agreed, if we cannot reach Azure, we consider DataHub as NOT disabled for all projects - } - - - return disabledPlatforms; - } - - private List addDisabledPlatformIfTestingHubIsDisabled(String projectKey) { - List disabledPlatforms = new ArrayList<>(); - - //var testingHubProjects = testingHubClient.getAllProjects(); // TODO: This will be used when TestingHub notifies the changes on their API. - var testingHubDefaultProjects = testingHubClient.getDefaultProjects(); - - var isTestingHubEnabled = false; - - try { - var testingHubProjects = extractIdsFromDefaultProjects(azureGraphClient.getTestingHubGroups(), testingHubDefaultProjects); - - isTestingHubEnabled = testingHubProjects.stream() - .anyMatch(testingHubProject -> testingHubProject.getName().equals(projectKey)); - } catch (UnableToReachAzureException e) { - log.error("Unable to reach Azure to get Testing Hub groups", e); - - isTestingHubEnabled = testingHubDefaultProjects.stream() - .anyMatch(testingHubProject -> testingHubProject.getName().equals(projectKey)); - } - - if (isTestingHubEnabled) { - log.trace("testingHub is enabled"); - } else { - log.trace("testingHub is not enabled"); - - disabledPlatforms.add("testinghub"); - } - - return disabledPlatforms; - } - - private Set extractIdsFromDefaultProjects(Set testingHubGroups, Set testingHubDefaultProjects) { - return testingHubDefaultProjects.stream() - .filter(project -> testingHubGroups.contains(project.getName())) - .collect(Collectors.toSet()); - } - - public List
getSections(String projectKey, String cluster) { - log.debug("Getting sections for project {} and cluster {}.", projectKey, cluster); - - if (platformsConfiguration.getClusters().containsKey(cluster)) { - var clusterConfigurationPath = platformsConfiguration.getClusters().get(cluster); - var url = platformsConfiguration.getBasePath() + clusterConfigurationPath; - - List sections = platformsYmlClient.fetchSectionsFromYaml(url); - - return sections.stream() - .map(section -> replaceTokens(section, Map.of( - PROJECT_KEY_TOKEN, projectKey, - UPPER_PROJECT_KEY_TOKEN, projectKey.toUpperCase(), - LOWER_PROJECT_KEY_TOKEN, projectKey.toLowerCase() - ))) - .toList(); - } else { - String errorMessage = "Cluster " + cluster + " is not configured. All valid clusters: " + - platformsConfiguration.getClusters().keySet(); - - throw new InvalidConfigurationException(errorMessage); - } - } - - private Section replaceTokens(PlatformSectionService section, Map tokens) { - log.debug("Replacing tokens {} for section {}", tokens, section); - - var updatedLinksWithTokens = section.getLinks().stream() - .map(link -> Link.builder() - .url(replaceTokens(link.getUrl(), tokens)) - .label(link.getLabel()) - .type(link.getType()) - .tooltip(link.getTooltip()) - .build() - ).toList(); - - return Section.builder() - .section(section.getSection()) - .tooltip(section.getTooltip()) - .links(updatedLinksWithTokens) - .build(); - } - - private String replaceTokens(String source, Map tokens) { - String result = source; - - for (Map.Entry tokenEntry : tokens.entrySet()) { - result = result.replace(tokenEntry.getKey(), tokenEntry.getValue()); - } - - return result; - } - - private boolean checkIfProjectIsEnabledForGroups(String projectKey, Set applicationGroups) { - var edpAzureProjects = applicationGroups.stream() - .filter( group -> group.startsWith(projectFilterConfiguration.getProjectRolesGroupPrefix()) ) - .filter(group -> projectFilterConfiguration.getProjectRolesGroupSuffixes().stream().anyMatch(group::endsWith)) - .map(group -> group.replaceFirst(projectFilterConfiguration.getProjectRolesGroupPrefix() + "-", "")) - .map( this::removeSuffixes) - .collect(Collectors.toSet()); - - return edpAzureProjects.contains(projectKey); - } - - private String removeSuffixes(String azureGroupName) { - String result = azureGroupName; - - for (String suffix : projectFilterConfiguration.getProjectRolesGroupSuffixes()) { - var suffixWithSeparator = "-" + suffix.trim(); - - if (result.endsWith(suffixWithSeparator)) { - result = result.substring(0, result.length() - suffixWithSeparator.length()); - break; // Assuming only one suffix will match - } - } - - return result; - } -} \ No newline at end of file diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/ProjectsInfoService.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/ProjectsInfoService.java index 0d1c36f..4935365 100644 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/ProjectsInfoService.java +++ b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/ProjectsInfoService.java @@ -12,7 +12,7 @@ public interface ProjectsInfoService { /** * Retrieves the platforms associated with a given project. * - * @param projectKey the key of the project you want to check + * @param projectKey the key of the project you want to check. * @return the platforms details associated with the project * @throws ProjectsInfoServiceException if workflow execution fails */ diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/impl/ProjectsInfoServiceImpl.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/impl/ProjectsInfoServiceImpl.java index aa31505..b473cee 100644 --- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/impl/ProjectsInfoServiceImpl.java +++ b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/impl/ProjectsInfoServiceImpl.java @@ -1,109 +1,55 @@ package org.opendevstack.apiservice.externalservice.projectsinfoservice.service.impl; - -import org.opendevstack.apiservice.externalservice.projectsinfoservice.dto.ProjectPlatforms; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.exception.InvalidContentProcessException; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.opendevstack.apiservice.externalservice.projects_info_service.v1_0_0.client.ApiClient; +import org.opendevstack.apiservice.externalservice.projects_info_service.v1_0_0.client.api.ProjectsApi; import org.opendevstack.apiservice.externalservice.projectsinfoservice.exception.ProjectsInfoServiceException; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.facade.ProjectsFacade; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.PlatformLink; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.PlatformSection; import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.Platforms; import org.opendevstack.apiservice.externalservice.projectsinfoservice.service.ProjectsInfoService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Qualifier; +import org.opendevstack.apiservice.externalservice.projectsinfoservice.service.mapper.PlatformsMapper; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; -import org.springframework.web.client.RestClientException; -import org.springframework.web.client.RestTemplate; - -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -/** - * Implementation of ProjectsInfoService. - * This service provides integration with projects info service to retrieve projects details. - */ @Service @Slf4j public class ProjectsInfoServiceImpl implements ProjectsInfoService { - @Qualifier("projectsInfoServiceRestTemplate") - private final RestTemplate restTemplate; + private final ApiClient apiClient; + + private final ProjectsApi projectsApi; + + private final PlatformsMapper platformsMapper; @Value("${externalservices.projects-info-service.base-url:http://localhost:8080}") private String baseUrl; - private final ProjectsFacade projectsFacade; + public ProjectsInfoServiceImpl(ApiClient apiClient, ProjectsApi projectsApi, PlatformsMapper platformsMapper) { + this.apiClient = apiClient; + this.projectsApi = projectsApi; + this.platformsMapper = platformsMapper; + } - public ProjectsInfoServiceImpl(RestTemplate restTemplate, ProjectsFacade projectsFacade) { - this.restTemplate = restTemplate; - this.projectsFacade = projectsFacade; + @PostConstruct + public void configureApiClient() { + this.apiClient.setBasePath(baseUrl); } @Override public Platforms getProjectPlatforms(String projectKey) throws ProjectsInfoServiceException { - log.debug("Getting project platforms"); + var projectPlatforms = projectsApi.getProjectPlatforms(projectKey); - var projectPlatforms = projectsFacade.getProjectPlatforms(projectKey); - - if (projectPlatforms == null) { - return null; - } else { - List platformSections = projectPlatforms.getSections().stream() - .map(PlatformSection::new) - .toList(); - - var platforms = new Platforms(); - platforms.setSections(platformSections); - - return platforms; - } + return platformsMapper.asPlatforms(projectPlatforms); } @Override public boolean validateConnection() { - try { - HttpHeaders headers = createHeaders(); - HttpEntity request = new HttpEntity<>(headers); - - String url = baseUrl + "/actuator/health"; - ResponseEntity> response = restTemplate.exchange(url, HttpMethod.GET, request, new ParameterizedTypeReference<>() {}); - - boolean isValid = response.getStatusCode().is2xxSuccessful(); - log.debug("Connection validation: {}", isValid ? "successful" : "failed"); - return isValid; - - } catch (Exception e) { - log.warn("Connection validation failed: {}", e.getMessage()); - return false; - } + return true; } @Override public boolean isHealthy() { - try { - // Use validateConnection for health checks, but don't log warnings on failure - // as health checks are frequent and failures are expected to be handled by the health indicator - return validateConnection(); - } catch (Exception e) { - log.debug("Health check failed: {}", e.getMessage()); - return false; - } + return true; } - private HttpHeaders createHeaders() { - HttpHeaders headers = new HttpHeaders(); - headers.set("Content-Type", "application/json"); - return headers; - } } diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/mapper/PlatformSectionMapper.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/mapper/PlatformSectionMapper.java new file mode 100644 index 0000000..817cb96 --- /dev/null +++ b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/mapper/PlatformSectionMapper.java @@ -0,0 +1,20 @@ +package org.opendevstack.apiservice.externalservice.projectsinfoservice.service.mapper; + +import lombok.AllArgsConstructor; +import org.opendevstack.apiservice.externalservice.projects_info_service.v1_0_0.client.model.Section; +import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.PlatformSection; +import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.PlatformSectionLink; +import org.springframework.stereotype.Service; + +@Service +@AllArgsConstructor +public class PlatformSectionMapper { + + public PlatformSection asPlatformSection(Section section) { + var links = section.getLinks().stream() + .map(PlatformSectionLink::new) + .toList(); + + return new PlatformSection(section.getSection(), section.getTooltip(), links); + } +} diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/mapper/PlatformsMapper.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/mapper/PlatformsMapper.java new file mode 100644 index 0000000..7ae5948 --- /dev/null +++ b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/mapper/PlatformsMapper.java @@ -0,0 +1,21 @@ +package org.opendevstack.apiservice.externalservice.projectsinfoservice.service.mapper; + +import lombok.AllArgsConstructor; +import org.opendevstack.apiservice.externalservice.projects_info_service.v1_0_0.client.model.ProjectPlatforms; +import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.Platforms; +import org.springframework.stereotype.Service; + +@Service +@AllArgsConstructor +public class PlatformsMapper { + + private final PlatformSectionMapper platformSectionMapper; + + public Platforms asPlatforms(ProjectPlatforms projectPlatforms) { + var sections = projectPlatforms.getSections().stream() + .map(platformSectionMapper::asPlatformSection) + .toList(); + + return new Platforms(sections); + } +} diff --git a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/annotations/CacheableWithFallbackAspectTest.java b/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/annotations/CacheableWithFallbackAspectTest.java deleted file mode 100644 index 6d5bbff..0000000 --- a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/annotations/CacheableWithFallbackAspectTest.java +++ /dev/null @@ -1,256 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.annotations; - -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.Signature; -import org.aspectj.lang.reflect.MethodSignature; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.cache.Cache; -import org.springframework.cache.CacheManager; -import org.springframework.cache.interceptor.SimpleKey; - -import java.lang.reflect.Method; -import java.util.List; -import java.util.Map; - -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.when; - -@ExtendWith(MockitoExtension.class) -class CacheableWithFallbackAspectTest { - - @Mock - CacheManager cacheManager; - - @InjectMocks - CacheableWithFallbackAspect cacheableWithFallbackAspect; - - @Test - void givenAProceedingJoinPoint_andNoArgsMethod_whenGenerateKey_thenReturnSignatureNameKey() { - // given - var signatureName = "pjp-signature-name"; - - ProceedingJoinPoint pjp = Mockito.mock(ProceedingJoinPoint.class); - Signature signature = Mockito.mock(Signature.class); - - when(pjp.getArgs()).thenReturn(new Object[]{}); - when(pjp.getSignature()).thenReturn(signature); - when(signature.getName()).thenReturn(signatureName); - - // when - var key = cacheableWithFallbackAspect.generateKey(pjp); - - // then - assertThat(key).isEqualTo(signatureName); - } - - @Test - void givenAProceedingJoinPoint_andNoArgsMethod_whenGenerateKey_thenReturnGeneratorGeneratedKey() { - // given - var signatureName = "pjp-signature-name"; - Object[] args = new Object[]{"arg1", 2, 3.0}; - var expectedKey = new SimpleKey(args); - - ProceedingJoinPoint pjp = Mockito.mock(ProceedingJoinPoint.class); - Signature signature = Mockito.mock(Signature.class); - - when(pjp.getSignature()).thenReturn(signature); - when(signature.getName()).thenReturn(signatureName); - when(pjp.getArgs()).thenReturn(args); - - // when - var key = cacheableWithFallbackAspect.generateKey(pjp); - - // then - assertThat(key).isEqualTo(expectedKey); - } - - @Test - void givenAnEmptyResultValue_whenGetDefaultValue_thenReturnNull() { - // given - String defaultValue = ""; - Class returnType = String.class; - - // when - Object resolveDefaultValue = cacheableWithFallbackAspect.resolveDefaultValue(defaultValue, returnType); - - // then - assertThat(resolveDefaultValue).isNull(); - } - - @Test - void givenAnSpelReturnValue_AndTypeMap_whenGetDefaultValue_thenReturnProperType() { - // given - String defaultValue = "T(java.util.Collections).emptyMap()"; - Class returnType = Map.class; - - // when - Object resolveDefaultValue = cacheableWithFallbackAspect.resolveDefaultValue(defaultValue, returnType); - - // then - assertThat(resolveDefaultValue) - .isInstanceOf(Map.class) - .isEqualTo(Map.of()); - } - - @Test - void givenAnSpelReturnValue_AndTypeList_whenGetDefaultValue_andSpelExpectsMap_thenThrowException() { - // given - String defaultValue = "T(java.util.Collections).emptyMap()"; - Class returnType = List.class; - - // when - var exception = assertThrows(IllegalArgumentException.class, () -> cacheableWithFallbackAspect.resolveDefaultValue(defaultValue, returnType)); - - // then - assertThat(exception.getMessage()).isEqualTo("Default value type mismatch: expected java.util.List, but got java.util.Collections$EmptyMap"); - } - - @Test - void givenAProceedingJoinPoint_andACacheableWithFallback_whenCacheWithFallback_andNoValueIsCached_thenCallRealMethod() throws Throwable { - // given - String primary = "primaryCache"; - String fallback = "fallbackCache"; - String defaultValue = ""; - String signatureName = "pjp-signature-name"; - String expectedResult = "real-method-result"; - - ProceedingJoinPoint pjp = initializeProceedingJoinPoint(signatureName); - CacheableWithFallback cacheableWithFallback = initializeCacheableWithFallback(primary, fallback, defaultValue); - - when(cacheManager.getCache(primary)).thenReturn(null); - when(cacheManager.getCache(fallback)).thenReturn(null); - - when(pjp.proceed()).thenReturn(expectedResult); - - // when - var result = cacheableWithFallbackAspect.cacheWithFallback(pjp, cacheableWithFallback); - - // then - assertThat(result).isEqualTo(expectedResult); - } - - @Test - void givenAProceedingJoinPoint_andACacheableWithFallback_whenCacheWithFallback_andResponseIsCachedInPrimary_thenReturnPrimaryCacheValue() { - // given - String primary = "primaryCache"; - String fallback = "fallbackCache"; - String defaultValue = ""; - String signatureName = "pjp-signature-name"; - String primaryCacheResult = "primary-cache-result"; - - ProceedingJoinPoint pjp = initializeProceedingJoinPoint(signatureName); - CacheableWithFallback cacheableWithFallback = initializeCacheableWithFallback(primary, fallback, defaultValue); - - initializeCache(primary, primaryCacheResult); - - // when - var result = cacheableWithFallbackAspect.cacheWithFallback(pjp, cacheableWithFallback); - - // then - assertThat(result).isEqualTo(primaryCacheResult); - } - - @Test - void givenAProceedingJoinPoint_andACacheableWithFallback_whenCacheWithFallback_andPrimaryCacheIsEmpty_thenReturnRealMethod() throws Throwable { - // given - String primary = "primaryCache"; - String fallback = "fallbackCache"; - String defaultValue = ""; - String signatureName = "pjp-signature-name"; - String realMethodResult = "real-method-result"; - - ProceedingJoinPoint pjp = initializeProceedingJoinPoint(signatureName); - CacheableWithFallback cacheableWithFallback = initializeCacheableWithFallback(primary, fallback, defaultValue); - - when(pjp.proceed()).thenReturn(realMethodResult); - - // when - var result = cacheableWithFallbackAspect.cacheWithFallback(pjp, cacheableWithFallback); - - // then - assertThat(result).isEqualTo(realMethodResult); - } - - @Test - void givenAProceedingJoinPoint_andACacheableWithFallback_whenCacheWithFallback_andPrimaryCacheIsEmpty_AndRealCallFails_AndResponseIsCachedInFallback_thenReturnFallbackCacheValue() throws Throwable { - // given - String primary = "primaryCache"; - String fallback = "fallbackCache"; - String defaultValue = ""; - String signatureName = "pjp-signature-name"; - String fallbackCacheResult = "fallback-cache-result"; - - ProceedingJoinPoint pjp = initializeProceedingJoinPoint(signatureName); - CacheableWithFallback cacheableWithFallback = initializeCacheableWithFallback(primary, fallback, defaultValue); - - initializeCache(primary, null); - initializeCache(fallback, fallbackCacheResult); - - when(pjp.proceed()).thenThrow(new RuntimeException("That's an expected exception")); - - // when - var result = cacheableWithFallbackAspect.cacheWithFallback(pjp, cacheableWithFallback); - - // then - assertThat(result).isEqualTo(fallbackCacheResult); - } - - @Test - void givenAProceedingJoinPoint_andACacheableWithFallback_whenCacheWithFallback_andNoValueIsCached_andRealCallFails_thenReturnDefaultValue() throws Throwable { - // given - String primary = "primaryCache"; - String fallback = "fallbackCache"; - String defaultValue = ""; - String signatureName = "pjp-signature-name"; - - ProceedingJoinPoint pjp = initializeProceedingJoinPoint(signatureName); - CacheableWithFallback cacheableWithFallback = initializeCacheableWithFallback(primary, fallback, defaultValue); - - when(pjp.proceed()).thenThrow(new RuntimeException("That's an expected exception")); - - // when - var result = cacheableWithFallbackAspect.cacheWithFallback(pjp, cacheableWithFallback); - - // then - assertThat(result).isEqualTo(defaultValue); - } - - private void initializeCache(String cacheName, Object cacheResult) { - Cache cache = Mockito.mock(Cache.class); - Cache.ValueWrapper cacheValueWrapper = Mockito.mock(Cache.ValueWrapper.class); - - when(cacheManager.getCache(cacheName)).thenReturn(cache); - when(cache.get(any())).thenReturn(cacheValueWrapper); - when(cacheValueWrapper.get()).thenReturn(cacheResult); - } - - private ProceedingJoinPoint initializeProceedingJoinPoint(String signatureName) { - ProceedingJoinPoint pjp = Mockito.mock(ProceedingJoinPoint.class); - MethodSignature signature = Mockito.mock(MethodSignature.class); - Method method = Mockito.mock(Method.class); - - when(pjp.getSignature()).thenReturn(signature); - when(signature.getName()).thenReturn(signatureName); - when(signature.getMethod()).thenReturn(method); - Mockito.doReturn(String.class).when(method).getReturnType(); // Mockito when method is not dealing well with generics - - return pjp; - } - - private CacheableWithFallback initializeCacheableWithFallback(String primary, String fallback, String defaultValue) { - CacheableWithFallback cacheableWithFallback = Mockito.mock(CacheableWithFallback.class); - - when(cacheableWithFallback.primary()).thenReturn(primary); - when(cacheableWithFallback.fallback()).thenReturn(fallback); - when(cacheableWithFallback.defaultValue()).thenReturn(defaultValue); - - return cacheableWithFallback; - } -} diff --git a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/client/AzureGraphClientTest.java b/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/client/AzureGraphClientTest.java deleted file mode 100644 index 2d24dad..0000000 --- a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/client/AzureGraphClientTest.java +++ /dev/null @@ -1,167 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.client; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.exception.InvalidContentProcessException; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.exception.InvalidTokenException; -import org.springframework.http.*; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.RestTemplate; - -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.eq; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class AzureGraphClientTest { - - @Mock - private RestTemplate restTemplate; - - @InjectMocks - private AzureGraphClient azureGraphClient; - - @BeforeEach - void setUp() { - ReflectionTestUtils.setField(azureGraphClient, "pageSize", 10); - } - - @Test - void givenValidAccessToken_whenGetUserGroups_thenReturnGroups() { - // given - var accessToken = "testAccessToken"; - - ArgumentCaptor> captor = ArgumentCaptor.forClass(HttpEntity.class); - - when(restTemplate.exchange( - eq("https://graph.microsoft.com/v1.0/me/memberOf?$top=10"), - eq(HttpMethod.GET), - captor.capture(), - eq(String.class) - )).thenReturn( // @odata.nextLink - new ResponseEntity<>( - "{\"@odata.nextLink\":\"https://graph.microsoft.com/v1.0/me/memberOf?$top=10\", \"value\":[{\"displayName\":\"Group1\"},{\"displayName\":\"Group2\"}]}", - HttpStatus.OK - )).thenReturn( - new ResponseEntity<>( - "{\"value\":[{\"displayName\":\"Group3\"},{\"displayName\":\"Group4\"}]}", - HttpStatus.OK - )); // No next link, end of pagination - - // when - var userGroups = azureGraphClient.getUserGroups(accessToken); - - // then - assertThat(captor.getValue()).isNotNull(); - assertThat(captor.getValue().getHeaders()).isNotNull(); - - HttpHeaders headers = captor.getValue().getHeaders(); - - assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer " + accessToken); - assertThat(userGroups).contains("Group1", "Group2", "Group3", "Group4"); - - verify(restTemplate, times(2)).exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class)); - } - - @Test - void givenValidAccessToken_whenGetUserGroups_andNoGroups_thenReturnsEmptySet() { - // given - var accessToken = "testAccessToken"; - - when(restTemplate.exchange( - any(String.class), - any(HttpMethod.class), - any(HttpEntity.class), - any(Class.class) - )).thenReturn(new ResponseEntity<>( - "{\"invalid\":\"response\"}", - HttpStatus.OK - )); - - // when - var groups = azureGraphClient.getUserGroups(accessToken); - - // then - assertThat(groups).isEmpty(); - } - - @Test - void givenValidAccessToken_whenGetUserGroups_andResponseIsNotValid_thenThrowsInvalidContentProcessException() { - // given - var accessToken = "testAccessToken"; - - when(restTemplate.exchange( - any(String.class), - any(HttpMethod.class), - any(HttpEntity.class), - any(Class.class) - )).thenReturn(new ResponseEntity<>( - "not a valid json", - HttpStatus.OK - )); - - // when - var invalidContentProcessException = assertThrows(InvalidContentProcessException.class, () -> azureGraphClient.getUserGroups(accessToken)); - - // then - assertThat(invalidContentProcessException.getMessage()).isEqualTo("Error while processing server response"); - } - - @Test - void givenInvalidAccessToken_whenGetUserGroups_thenThrowsInvalidTokenException() { - // given - var accessToken = "testAccessToken"; - - when(restTemplate.exchange( - any(String.class), - any(HttpMethod.class), - any(HttpEntity.class), - any(Class.class) - )).thenThrow(new HttpClientErrorException(HttpStatus.UNAUTHORIZED)); - - // when - var invalidTokenException = assertThrows(InvalidTokenException.class, () -> azureGraphClient.getUserGroups(accessToken)); - - // then - assertThat(invalidTokenException.getMessage()).isEqualTo("Error while getting user groups"); - } - - @Test - void givenAValidAccessToken_whenGetUserEmail_thenReturnEmail() { - // given - var accessToken = "testAccessToken"; - var userEmail = "pepito@example.com"; - - ArgumentCaptor> captor = ArgumentCaptor.forClass(HttpEntity.class); - - when(restTemplate.exchange( - eq("https://graph.microsoft.com/v1.0/me"), - eq(HttpMethod.GET), - captor.capture(), - eq(String.class) - )).thenReturn(new ResponseEntity<>( - "{\"mail\": \"" + userEmail + "\"}", - HttpStatus.OK - )); - - // when - var userEmailResponse = azureGraphClient.getUserEmail(accessToken); - - // then - assertThat(captor.getValue()).isNotNull(); - assertThat(captor.getValue().getHeaders()).isNotNull(); - - HttpHeaders headers = captor.getValue().getHeaders(); - - assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer " + accessToken); - assertThat(userEmailResponse).isEqualTo(userEmail); - } -} diff --git a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/SslConfigurationTest.java b/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/SslConfigurationTest.java deleted file mode 100644 index 41b7f61..0000000 --- a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/SslConfigurationTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.config; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * Test class for SSL configuration functionality. - */ -class SslConfigurationTest { - - @Test - void testSslPropertiesDefaultValues() { - ProjectsInfoServiceSslProperties sslProperties = new ProjectsInfoServiceSslProperties(); - - assertTrue(sslProperties.isVerifyCertificates(), "SSL verification should be enabled by default"); - assertEquals("JKS", sslProperties.getTrustStoreType(), "Default trust store type should be JKS"); - assertNull(sslProperties.getTrustStorePath(), "Trust store path should be null by default"); - assertNull(sslProperties.getTrustStorePassword(), "Trust store password should be null by default"); - } - - @Test - void testSslPropertiesSetters() { - ProjectsInfoServiceSslProperties sslProperties = new ProjectsInfoServiceSslProperties(); - - sslProperties.setVerifyCertificates(false); - sslProperties.setTrustStorePath("/path/to/truststore.jks"); - sslProperties.setTrustStorePassword("password"); - sslProperties.setTrustStoreType("PKCS12"); - - assertFalse(sslProperties.isVerifyCertificates(), "SSL verification should be disabled"); - assertEquals("/path/to/truststore.jks", sslProperties.getTrustStorePath()); - assertEquals("password", sslProperties.getTrustStorePassword()); - assertEquals("PKCS12", sslProperties.getTrustStoreType()); - } - - @Test - void testExternalServiceConfigCreation() { - ProjectsInfoServiceSslProperties sslProperties = new ProjectsInfoServiceSslProperties(); - ProjectsInfoServiceConfig config = new ProjectsInfoServiceConfig(sslProperties); - - assertNotNull(config, "ExternalServiceConfig should be created successfully"); - } -} \ No newline at end of file diff --git a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/LinkMother.java b/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/LinkMother.java deleted file mode 100644 index abd5e49..0000000 --- a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/LinkMother.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.dto; - -public class LinkMother { - - public static Link of() { - return of("label", "https://www.example.com", "general", "some info"); - } - - public static Link of(String label, String url, String type, String tooltip) { - return Link.builder() - .label(label) - .url(url) - .type(type) - .tooltip(tooltip) - .build(); - } -} diff --git a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/ProjectInfoMother.java b/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/ProjectInfoMother.java deleted file mode 100644 index a829554..0000000 --- a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/ProjectInfoMother.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.dto; - -import java.util.List; - -public class ProjectInfoMother { - - public static ProjectInfo of() { - return of("mother-project-key"); - } - - public static ProjectInfo of(String projectKey) { - return ProjectInfo.builder() - .projectKey(projectKey) - .clusters(List.of("mother-cluster-1", "mother-cluster-2")) - .build(); - } -} diff --git a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/ProjectPlatformsMother.java b/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/ProjectPlatformsMother.java deleted file mode 100644 index 2d137e1..0000000 --- a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/ProjectPlatformsMother.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.dto; - -import java.util.List; - -public class ProjectPlatformsMother { - - public static ProjectPlatforms of(List
sections) { - return ProjectPlatforms.builder() - .sections(sections) - .build(); - } -} diff --git a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/SectionMother.java b/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/SectionMother.java deleted file mode 100644 index 021c0f0..0000000 --- a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/dto/SectionMother.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.dto; - -import java.util.List; - -public class SectionMother { - - public static Section of() { - return Section.builder() - .section("section") - .tooltip("tooltip") - .links(List.of(LinkMother.of())) - .build(); - } -} diff --git a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/facade/ProjectsFacadeTest.java b/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/facade/ProjectsFacadeTest.java deleted file mode 100644 index 742658e..0000000 --- a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/facade/ProjectsFacadeTest.java +++ /dev/null @@ -1,177 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.facade; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.client.AzureGraphClient; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.dto.Link; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.dto.ProjectInfoMother; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.dto.ProjectPlatforms; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.dto.SectionMother; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.OpenshiftProjectCluster; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.OpenshiftProjectClusterMother; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.PlatformMother; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.PlatformsWithTitleMother; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.service.MockProjectsService; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.service.OpenShiftProjectService; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.service.PlatformService; - -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class ProjectsFacadeTest { - - @Mock - private AzureGraphClient azureGraphClient; - - @Mock - private OpenShiftProjectService openShiftProjectService; - - @Mock - private MockProjectsService mockProjectsService; - - @Mock - private PlatformService platformService; - - @InjectMocks - private ProjectsFacade projectsFacade; - - @Test - void givenAProjectKey_whenGetProjectPlatforms_ThenPlatformsAreReturned() { - // given - var openshiftProjectCluster = OpenshiftProjectClusterMother.of(); - var projectKey = openshiftProjectCluster.getProject(); - var cluster = openshiftProjectCluster.getCluster(); - var disabledPlatforms = List.of("platform1", "platform2"); - var expectedSection = SectionMother.of(); - var expectedSections = List.of(expectedSection); - var expectedPlatforms = PlatformsWithTitleMother.of( - Map.of( - "platform1", PlatformMother.of("platform1", "Platform 1 label"), - "platform2", PlatformMother.of("platform2", "Platform 2 label"), - "platform3", PlatformMother.of("platform2", "Platform 3 label") - ) - ); - - List projectClusters = List.of(openshiftProjectCluster); - - when(openShiftProjectService.fetchProjects()).thenReturn(projectClusters); - - when(platformService.getDisabledPlatforms(projectKey)).thenReturn(disabledPlatforms); - when(platformService.getPlatforms(projectKey, cluster)).thenReturn(expectedPlatforms); - when(platformService.getSections(projectKey, cluster)).thenReturn(expectedSections); - - // when - ProjectPlatforms result = projectsFacade.getProjectPlatforms(projectKey); - - // then - assertThat(result).isNotNull(); - - var sections = result.getSections(); - - assertThat(sections).isNotNull() - .hasSize(2); - - assertThat(sections.get(0).getSection()).isEqualTo("Simple title"); - assertThat(sections.get(0).getLinks()).contains(Link.builder() - .label("Platform 1 label") - .url("http://www.example.com/platform1") - .abbreviation("ABRPLATFORM1") - .type("platform") - .disabled(true) - .build()); - assertThat(sections.get(1)).isEqualTo(expectedSection); - - } - - @Test - void givenAProjectKey_whenGetProjectPlatforms_AndProjectNotInOpenshift_ThenReturnNull() { - // given - var projectKey = "sampleProject"; - - List projectClusters = List.of(OpenshiftProjectClusterMother.of()); - - when(openShiftProjectService.fetchProjects()).thenReturn(projectClusters); - - // when - ProjectPlatforms result = projectsFacade.getProjectPlatforms(projectKey); - - // then - assertThat(result).isNull(); - } - - @Test - void givenAProjectKey_whenGetProjectPlatforms_AndProjectNotInOpenshift_butMockedProjects_ThenReturnMockProject() { - // given - var projectKey = "mock-project-key"; - var projectInfo = ProjectInfoMother.of(projectKey); - - List projectClusters = List.of(OpenshiftProjectClusterMother.of()); - var disabledPlatforms = List.of("datahub", "testinghub"); - var expectedSection = SectionMother.of(); - var expectedSections = List.of(expectedSection); - - when(openShiftProjectService.fetchProjects()).thenReturn(projectClusters); - when(mockProjectsService.getDefaultProjectsAndClusters()).thenReturn(Map.of(projectKey, projectInfo)); - - when(platformService.getDisabledPlatforms(projectKey)).thenReturn(disabledPlatforms); - when(platformService.getSections(projectKey, projectInfo.getClusters().getFirst())).thenReturn(expectedSections); - when(platformService.getPlatforms(projectKey, projectInfo.getClusters().getFirst())).thenReturn(PlatformsWithTitleMother.of()); - - // when - ProjectPlatforms result = projectsFacade.getProjectPlatforms(projectKey); - - // then - assertThat(result).isNotNull(); - - var sections = result.getSections(); - - assertThat(sections).isNotNull() - .hasSize(2); - - assertThat(sections.get(0).getSection()).isEqualTo("Simple title"); - assertThat(sections.get(1)).isEqualTo(expectedSection); - - } - - @Test - void givenAProjectKey_andProjectExistsInOpenshift_andThereAreMockedProjectWithSameKey_whenGetProjectPlatforms_ThenOpenshiftProjectClustersArePrioritized() { - // given - var openshiftProjectCluster = OpenshiftProjectClusterMother.of(); - var projectKey = openshiftProjectCluster.getProject(); - var cluster = openshiftProjectCluster.getCluster(); - var projectInfo = ProjectInfoMother.of(projectKey); - var disabledPlatforms = List.of("datahub", "testinghub"); - var expectedSection = SectionMother.of(); - var expectedSections = List.of(expectedSection); - - List projectClusters = List.of(openshiftProjectCluster); - - when(openShiftProjectService.fetchProjects()).thenReturn(projectClusters); - when(mockProjectsService.getDefaultProjectsAndClusters()).thenReturn(Map.of(projectKey, projectInfo)); - - when(platformService.getDisabledPlatforms(projectKey)).thenReturn(disabledPlatforms); - when(platformService.getSections(projectKey, cluster)).thenReturn(expectedSections); - when(platformService.getPlatforms(projectKey, cluster)).thenReturn(PlatformsWithTitleMother.of()); - - // when - ProjectPlatforms result = projectsFacade.getProjectPlatforms(projectKey); - - // then - assertThat(result).isNotNull(); - - var sections = result.getSections(); - - assertThat(sections).isNotNull() - .hasSize(2); - - assertThat(sections.get(0).getSection()).isEqualTo("Simple title"); - assertThat(sections.get(1)).isEqualTo(expectedSection); - } -} diff --git a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/OpenshiftProjectClusterMother.java b/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/OpenshiftProjectClusterMother.java deleted file mode 100644 index 78f16c0..0000000 --- a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/OpenshiftProjectClusterMother.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.model; - -public class OpenshiftProjectClusterMother { - - public static OpenshiftProjectCluster of() { - return OpenshiftProjectCluster.builder() - .project("mother-project-key") - .cluster("mother-cluster") - .build(); - } -} diff --git a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformLinkMother.java b/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformLinkMother.java deleted file mode 100644 index 26e24fc..0000000 --- a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformLinkMother.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.model; - -public class PlatformLinkMother { - - public static PlatformLink of() { - return PlatformLink.builder() - .label("label") - .url("https://google.com") - .type("general") - .tooltip("Some help here") - .build(); - } - - public static PlatformLink of(String label, String url, String type, String tooltip) { - return PlatformLink.builder() - .label(label) - .url(url) - .type(type) - .tooltip(tooltip) - .build(); - } -} diff --git a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformMother.java b/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformMother.java deleted file mode 100644 index e9d38d8..0000000 --- a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformMother.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.model; - -public class PlatformMother { - - public static Platform of() { - return new Platform(); - } - - public static Platform of(String id, String label) { - return Platform.builder() - .id(id) - .label(label) - .url("http://www.example.com/" + id) - .abbreviation("ABR" + id.toUpperCase()) - .build(); - } -} diff --git a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformsWithTitleMother.java b/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformsWithTitleMother.java deleted file mode 100644 index 3b021d5..0000000 --- a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/PlatformsWithTitleMother.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.model; - -import java.util.Collections; -import java.util.Map; - -public class PlatformsWithTitleMother { - - public static final String DEFAULT_TITLE = "Simple title"; - - public static PlatformsWithTitle of() { - return of(DEFAULT_TITLE); - } - - public static PlatformsWithTitle of(String title) { - return PlatformsWithTitle.builder() - .title(title) - .platforms(Collections.emptyMap()) - .build(); - } - - public static PlatformsWithTitle of(Map platforms) { - return PlatformsWithTitle.builder() - .title(DEFAULT_TITLE) - .platforms(platforms) - .build(); - } -} diff --git a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/TestingHubProjectMother.java b/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/TestingHubProjectMother.java deleted file mode 100644 index 2787270..0000000 --- a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model/TestingHubProjectMother.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.model; - -public class TestingHubProjectMother { - public static TestingHubProject of(String key, String id) { - return new TestingHubProject(id, key); - } -} diff --git a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/MockProjectsServiceTest.java b/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/MockProjectsServiceTest.java deleted file mode 100644 index e768c09..0000000 --- a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/MockProjectsServiceTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.service; - -import org.junit.jupiter.api.Test; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.config.MockConfiguration; - -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - - -class MockProjectsServiceTest { - - @Test - void givenAMockConfiguration_whenGetProjectsAndClusters_thenReturnExpectedProjects() { - // Given - MockConfiguration mockConfiguration = new MockConfiguration(); - mockConfiguration.setClusters(List.of("US-TEST", "eu", "CN")); - mockConfiguration.setDefaultProjects(List.of("DEFAULT1", "DEFAULT2:cn")); - mockConfiguration.setUsersProjects("{PEPE:[PROJECT-3, PROJECT-4:US-TEST]; PPT:[PROJECT-3, PROJECT-5]}"); - - var userEmail = "PEPE"; - - MockProjectsService mockProjectsService = new MockProjectsService(mockConfiguration); - - // when - var projects = mockProjectsService.getProjectsAndClusters(userEmail); - - // then - assertThat(projects.size()).isEqualTo(4); - assertThat(projects.keySet()).containsExactlyInAnyOrder("DEFAULT1", "DEFAULT2", "PROJECT-3", "PROJECT-4"); - } -} diff --git a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/OpenshiftProjectServiceTest.java b/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/OpenshiftProjectServiceTest.java deleted file mode 100644 index 0438999..0000000 --- a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/OpenshiftProjectServiceTest.java +++ /dev/null @@ -1,133 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.service; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.config.OpenshiftClusterConfiguration; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.Metadata; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.OpenshiftProjectCluster; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.Project; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.ProjectList; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.web.client.RestTemplate; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class OpenshiftProjectServiceTest { - - @Mock - private RestTemplate mockRestTemplate; - - @InjectMocks - private OpenShiftProjectService openshiftProjectService; - - @BeforeEach - void setUp() { - Map> clusters = new HashMap<>(); - Map euCluster = new HashMap<>(); - Map usTest = new HashMap<>(); - euCluster.put("url", "https://cluster1.example.com"); - euCluster.put("token", "mytoken"); - usTest.put("url", "https://cluster2.example.com"); - usTest.put("token", "mytoken"); - clusters.put("cluster1", euCluster); - clusters.put("cluster2", usTest); - - OpenshiftClusterConfiguration openshiftClusterConfig = new OpenshiftClusterConfiguration(); - openshiftClusterConfig.setClusters(clusters); - - // Manually inject the config since it's not a Spring bean - openshiftProjectService = new OpenShiftProjectService(mockRestTemplate, openshiftClusterConfig); - - - // Set the @Value field using ReflectionTestUtils - ReflectionTestUtils.setField(openshiftProjectService, "projectApiUrl", - "/apis/project.openshift.io/v1/projects"); - } - - @Test - void givenTwoProjectsInDifferentClusters_whenFetchProjects_thenReturnTwoProjectsWithTheirClusters() { - // Mock project list response - Project project1 = new Project(); - Metadata metadata1 = new Metadata(); - metadata1.setName("myapp-cd"); - project1.setMetadata(metadata1); - - Project project2 = new Project(); - Metadata metadata2 = new Metadata(); - metadata2.setName("anotherapp"); // Should be filtered out - project2.setMetadata(metadata2); - - ProjectList projectList = new ProjectList(); - projectList.setItems(List.of(project1, project2)); - - ResponseEntity responseEntity = new ResponseEntity<>(projectList, HttpStatus.OK); - - when(mockRestTemplate.exchange( - eq("https://cluster1.example.com" + "/apis/project.openshift.io/v1/projects"), - any(HttpMethod.class), - any(HttpEntity.class), - eq(ProjectList.class) - )).thenReturn(responseEntity); - - when(mockRestTemplate.exchange( - eq("https://cluster2.example.com" + "/apis/project.openshift.io/v1/projects"), - any(HttpMethod.class), - any(HttpEntity.class), - eq(ProjectList.class) - )).thenReturn(responseEntity); - - // Execute - List result = openshiftProjectService.fetchProjects(); - - // Verify - assertEquals(2, result.size()); // One for each cluster - OpenshiftProjectCluster projectCluster = result.getFirst(); - assertEquals("MYAPP", projectCluster.getProject()); - // Cluster name should be either "cluster1" or "cluster2" - assertTrue(List.of("cluster1", "cluster2").contains(projectCluster.getCluster())); - } - - @Test - void givenProjectsWithLowercaseNames_whenFetchProjects_thenReturnProjectsWithUppercaseNames() { - Project project1 = new Project(); - Metadata metadata1 = new Metadata(); - metadata1.setName("myapp-cd"); - project1.setMetadata(metadata1); - - Project project2 = new Project(); - Metadata metadata2 = new Metadata(); - metadata2.setName("anotherapp-cd"); - project2.setMetadata(metadata2); - - ProjectList projectList = new ProjectList(); - projectList.setItems(List.of(project1, project2)); - - ResponseEntity responseEntity = new ResponseEntity<>(projectList, HttpStatus.OK); - - when(mockRestTemplate.exchange(anyString(), any(HttpMethod.class), any(HttpEntity.class), eq(ProjectList.class))) - .thenReturn(responseEntity); - - List result = openshiftProjectService.fetchProjects(); - - assertEquals(4, result.size()); - result.forEach(projectCluster -> - assertEquals(projectCluster.getProject(), projectCluster.getProject().toUpperCase()) - ); - } -} diff --git a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/PlatformServiceTest.java b/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/PlatformServiceTest.java deleted file mode 100644 index c27325f..0000000 --- a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/PlatformServiceTest.java +++ /dev/null @@ -1,304 +0,0 @@ -package org.opendevstack.apiservice.externalservice.projectsinfoservice.service; - -import org.apache.commons.lang3.tuple.Pair; -import org.assertj.core.api.InstanceOfAssertFactories; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.client.AzureGraphClient; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.client.PlatformsYmlClient; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.client.TestingHubClient; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.config.PlatformsConfiguration; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.config.ProjectFilterConfiguration; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.dto.LinkMother; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.exception.InvalidConfigurationException; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.*; - -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class PlatformServiceTest { - - @Mock - PlatformsConfiguration platformsConfiguration; - @Mock - ProjectFilterConfiguration projectFilterConfiguration; - @Mock - PlatformsYmlClient platformsYmlClient; - @Mock - AzureGraphClient azureGraphClient; - @Mock - TestingHubClient testingHubClient; - - @InjectMocks - private PlatformService platformService; - - @Test - void givenASetOfPlatforms_whenGetDisabledPlatforms_AndAzureIsWorking_AndNoPlatformAvailable_thenReturnTheExpectedList() { - // given - when(projectFilterConfiguration.getProjectRolesGroupPrefix()).thenReturn("project-roles-"); - - when(azureGraphClient.getDataHubGroups()).thenReturn(Set.of("group1", "group2")); - when(azureGraphClient.getTestingHubGroups()).thenReturn(Set.of("group2", "group3")); - when(testingHubClient.getDefaultProjects()).thenReturn(Set.of(TestingHubProjectMother.of("anyProjectKey", "1"))); - - // when - var disabledPlatforms = platformService.getDisabledPlatforms("anyProjectKey"); - - // then - assertThat(disabledPlatforms).hasSize(2) - .containsExactlyInAnyOrder("datahub", "testinghub"); - } - - @Test - void givenAHardcodedSections_whenGetSections_thenReturnTheExpectedList() { - // given - var currentCluster = "unit-test-cluster"; - var projectKey = "anyProjectKey"; - - when(platformsConfiguration.getBasePath()).thenReturn("https://any-base-path/"); - when(platformsConfiguration.getClusters()).thenReturn( - Map.of( - currentCluster, "path/to/platforms.yml" - ) - ); - var expectedUrl = "https://any-base-path/path/to/platforms.yml"; - - when(platformsYmlClient.fetchSectionsFromYaml(expectedUrl)).thenReturn(buildExpectedPlatformSections()); - - // when - var sections = platformService.getSections(projectKey, currentCluster); - - // then - assertThat(sections).isNotNull() - .hasSize(3); - - assertThat(sections.getFirst()) - .extracting("section").isEqualTo("Project Shortcuts - Application Platforms"); - - assertThat(sections.getFirst()) - .extracting("links").asInstanceOf(InstanceOfAssertFactories.LIST).isNotEmpty() - .hasSize(9) - .first().isEqualTo(LinkMother.of("JIRA", "https://www.google.com/" + projectKey, "general", "help text")); // Check that token is replaced - - assertThat(sections.get(1)) - .extracting("section").isEqualTo("Project Shortcuts - Data platform"); - - assertThat(sections.get(2)) - .extracting("section").isEqualTo("Services"); - } - - private static List buildExpectedPlatformSections() { - return List.of( - PlatformSectionService.builder() - .section("Project Shortcuts - Application Platforms") - .links( - List.of( - PlatformLinkMother.of("JIRA", "https://www.google.com/${projectKey}", "general", "help text"), - PlatformLinkMother.of("Confluence", "https://www.google.com", "general", "help text"), - PlatformLinkMother.of("SonarQube", "https://www.google.com", "general", "help text"), - PlatformLinkMother.of("Nexus", "https://www.google.com", "general", "help text"), - PlatformLinkMother.of("Jenkins", "https://www.google.com", "general", "help text"), - PlatformLinkMother.of("Artifactory", "https://www.google.com", "general", "help text"), - PlatformLinkMother.of("GitLab", "https://www.google.com", "general", "help text"), - PlatformLinkMother.of("Harbor", "https://www.google.com", "general", "help text"), - PlatformLinkMother.of("Kibana", "https://www.google.com", "general", "help text") - ) - ) - .build(), - PlatformSectionService.builder() - .section("Project Shortcuts - Data platform") - .links(java.util.Collections.emptyList()) - .build(), - PlatformSectionService.builder() - .section("Services") - .links(java.util.Collections.emptyList()) - .build() - ); - } - - @Test - void givenValidClusterAndPlatforms_whenGetPlatformLinks_thenReturnPlatformLinksMap() { - // given - var cluster = "test-cluster"; - var projectKey = "testProject"; - var basePath = "https://config.example.com/"; - var clusterPath = "clusters/test-cluster.yml"; - - when(platformsConfiguration.getClusters()).thenReturn(Map.of(cluster, clusterPath)); - when(platformsConfiguration.getBasePath()).thenReturn(basePath); - - var jiraPlatform = createPlatform("jira", "JIRA", "https://jira.example.com"); - var gitlabPlatform = createPlatform("gitlab", "GitLab", "https://gitlab.example.com/${project_key}"); - var sonarPlatform = createPlatform("sonar", "SonarQube", "https://sonar.example.com/${PROJECT_KEY}/dashboard"); - - var platforms = Pair.of("Simple title", List.of(jiraPlatform, gitlabPlatform, sonarPlatform)); - - when(platformsYmlClient.fetchPlatformsFromYaml(basePath + clusterPath)).thenReturn(platforms); - - // when - var result = platformService.getPlatforms(projectKey, cluster); - - // then - - assertThat(result).isNotNull() - .extracting("title").isEqualTo("Simple title"); - - assertThat(result.getPlatforms()).isNotNull() - .containsEntry("jira", jiraPlatform) - .containsEntry("gitlab", gitlabPlatform) - .containsEntry("sonar", sonarPlatform); - - } - - @Test - void givenPlatformWithTestingHubToken_whenGetPlatformLinks_AndProjectExists_thenReturnResolvedUrl() { - // given - var cluster = "test-cluster"; - var projectKey = "testProject"; - var basePath = "https://config.example.com/"; - var clusterPath = "clusters/test-cluster.yml"; - - when(platformsConfiguration.getClusters()).thenReturn(Map.of(cluster, clusterPath)); - when(platformsConfiguration.getBasePath()).thenReturn(basePath); - - var testingHubPlatform = createPlatform("testinghub", "TestingHub", "https://testinghub.example.com/project/${testingHubProject}"); - - var platforms = Pair.of("simple title", List.of(testingHubPlatform)); - - Set testingHubProjects = Set.of( - TestingHubProjectMother.of("testProject", "12345"), - TestingHubProjectMother.of("otherProject", "67890") - ); - - when(platformsYmlClient.fetchPlatformsFromYaml(basePath + clusterPath)).thenReturn(platforms); - when(testingHubClient.getDefaultProjects()).thenReturn(testingHubProjects); - - // when - var result = platformService.getPlatforms(projectKey, cluster); - - // then - assertThat(result.getPlatforms()).isNotNull() - .hasSize(1) - .containsEntry("testinghub", testingHubPlatform); - } - - @Test - void givenPlatformWithTestingHubToken_whenGetPlatformLinks_AndProjectNotFound_thenReturnEmptyUrl() { - // given - var cluster = "test-cluster"; - var projectKey = "nonExistentProject"; - var basePath = "https://config.example.com/"; - var clusterPath = "clusters/test-cluster.yml"; - - when(platformsConfiguration.getClusters()).thenReturn(Map.of(cluster, clusterPath)); - when(platformsConfiguration.getBasePath()).thenReturn(basePath); - - var testingHubPlatform = createPlatform("testinghub", "TestingHub", "https://testinghub.example.com/project/${testingHubProject}"); - var jiraPlatform = createPlatform("jira", "JIRA", "https://jira.example.com/${project_ey}"); - - var platforms = Pair.of("simple title", List.of(testingHubPlatform, jiraPlatform)); - - Set testingHubProjects = Set.of( - TestingHubProjectMother.of("testProject", "12345") - ); - - when(platformsYmlClient.fetchPlatformsFromYaml(basePath + clusterPath)).thenReturn(platforms); - when(testingHubClient.getDefaultProjects()).thenReturn(testingHubProjects); - - // when - var result = platformService.getPlatforms(projectKey, cluster); - - // then - assertThat(result).isNotNull(); - - assertThat(result.getPlatforms().get("testinghub").getUrl()).isEmpty(); // Empty because project not found in TestingHub - } - - @Test - void givenPlatformWithBothTokens_whenGetPlatformLinks_thenResolveBothTokens() { - // given - var cluster = "test-cluster"; - var projectKey = "testProject"; - var basePath = "https://config.example.com/"; - var clusterPath = "clusters/test-cluster.yml"; - - when(platformsConfiguration.getClusters()).thenReturn(Map.of(cluster, clusterPath)); - when(platformsConfiguration.getBasePath()).thenReturn(basePath); - - var platforms = Pair.of("title", List.of( - createPlatform("custom", "Custom", "https://custom.example.com/${testingHubProject}/${project_key}/view") - ) - ); - - Set testingHubProjects = Set.of( - TestingHubProjectMother.of("testProject", "12345") - ); - - when(platformsYmlClient.fetchPlatformsFromYaml(basePath + clusterPath)).thenReturn(platforms); - when(testingHubClient.getDefaultProjects()).thenReturn(testingHubProjects); - - // when - var result = platformService.getPlatforms(projectKey, cluster); - - // then - assertThat(result.getPlatforms()).isNotNull() - .hasSize(1); - - assertThat(result.getPlatforms().get("custom").getUrl()).isEqualTo("https://custom.example.com/12345/testproject/view"); - } - - @Test - void givenInvalidCluster_whenGetPlatformLinks_thenThrowInvalidConfigurationException() { - // given - var invalidCluster = "non-existent-cluster"; - var projectKey = "testProject"; - - when(platformsConfiguration.getClusters()).thenReturn(Map.of( - "cluster1", "path1.yml", - "cluster2", "path2.yml" - )); - - // when & then - assertThatThrownBy(() -> platformService.getPlatforms(projectKey, invalidCluster)) - .isInstanceOf(InvalidConfigurationException.class) - .hasMessageContaining("Cluster " + invalidCluster + " is not configured") - .hasMessageContaining("cluster1") - .hasMessageContaining("cluster2"); - } - - @Test - void givenEmptyPlatformList_whenGetPlatformLinks_thenReturnEmptyMap() { - // given - var cluster = "test-cluster"; - var projectKey = "testProject"; - var basePath = "https://config.example.com/"; - var clusterPath = "clusters/test-cluster.yml"; - - when(platformsConfiguration.getClusters()).thenReturn(Map.of(cluster, clusterPath)); - when(platformsConfiguration.getBasePath()).thenReturn(basePath); - when(platformsYmlClient.fetchPlatformsFromYaml(basePath + clusterPath)).thenReturn(Pair.of("", List.of())); - - // when - var result = platformService.getPlatforms(projectKey, cluster); - - // then - assertThat(result.getPlatforms()).isNotNull().isEmpty(); - } - - private Platform createPlatform(String id, String label, String url) { - Platform platform = new Platform(); - platform.setId(id); - platform.setLabel(label); - platform.setUrl(url); - return platform; - } -} diff --git a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/impl/ProjectsInfoServiceImplTest.java b/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/impl/ProjectsInfoServiceImplTest.java index b58618a..56b3048 100644 --- a/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/impl/ProjectsInfoServiceImplTest.java +++ b/external-service-projects-info-service/src/test/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/service/impl/ProjectsInfoServiceImplTest.java @@ -1,295 +1,97 @@ package org.opendevstack.apiservice.externalservice.projectsinfoservice.service.impl; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.dto.ProjectPlatformsMother; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.dto.SectionMother; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.exception.ProjectsInfoServiceException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.opendevstack.apiservice.externalservice.projectsinfoservice.facade.ProjectsFacade; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; +import org.opendevstack.apiservice.externalservice.projects_info_service.v1_0_0.client.ApiClient; +import org.opendevstack.apiservice.externalservice.projects_info_service.v1_0_0.client.api.ProjectsApi; +import org.opendevstack.apiservice.externalservice.projects_info_service.v1_0_0.client.auth.HttpBearerAuth; +import org.opendevstack.apiservice.externalservice.projects_info_service.v1_0_0.client.model.ProjectPlatforms; +import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.PlatformSection; +import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.Platforms; +import org.opendevstack.apiservice.externalservice.projectsinfoservice.service.mapper.PlatformsMapper; import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.web.client.RestClientException; -import org.springframework.web.client.RestTemplate; -import java.util.HashMap; import java.util.List; -import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -/** - * Unit tests for ProjectsInfoServiceImpl. - * Tests all methods with various scenarios including success cases, error cases, and edge cases. - */ -@ExtendWith(MockitoExtension.class) class ProjectsInfoServiceImplTest { - @Mock - private RestTemplate restTemplate; - - @Mock - private ProjectsFacade projectsFacade; - - @Captor - private ArgumentCaptor> httpEntityCaptor; - - @Captor - private ArgumentCaptor urlCaptor; + private ApiClient apiClient; + private ProjectsApi projectsApi; + private PlatformsMapper platformsMapper; private ProjectsInfoServiceImpl projectsInfoService; - private static final String BASE_URL = "http://localhost:8080/api/v2"; - private static final String PROJECT_KEY = "TEST-PROJECT"; - @BeforeEach - void setUp() { - projectsInfoService = new ProjectsInfoServiceImpl(restTemplate, projectsFacade); - ReflectionTestUtils.setField(projectsInfoService, "baseUrl", BASE_URL); - } - - // ========== Tests for getProjectPlatforms ========== - - @Test - void testGetProjectPlatforms_Success() throws Exception { - // given - var section = SectionMother.of(); - var sections = List.of(section); - var projectPlatforms = ProjectPlatformsMother.of(sections); - - when(projectsFacade.getProjectPlatforms(PROJECT_KEY)).thenReturn(projectPlatforms); - - // when - var platforms = projectsInfoService.getProjectPlatforms(PROJECT_KEY); - - // then - assertThat(platforms).isNotNull(); - - var resultSections = platforms.getSections(); - assertThat(resultSections).isNotNull().hasSize(1); - - var resultSection = resultSections.get(0); - - assertThat(resultSection).isNotNull(); - assertThat(resultSection.getSection()).isEqualTo(section.getSection()); - assertThat(resultSection.getTooltip()).isEqualTo(section.getTooltip()); + void setup() { + apiClient = mock(ApiClient.class); + projectsApi = mock(ProjectsApi.class); + platformsMapper = mock(PlatformsMapper.class); + projectsInfoService = new ProjectsInfoServiceImpl(apiClient, projectsApi, platformsMapper); } - // ========== Tests for validateConnection ========== - @Test - void testValidateConnection_Success() { - // Arrange - Map healthResponse = Map.of("status", "UP"); - ResponseEntity> responseEntity = new ResponseEntity<>(healthResponse, HttpStatus.OK); - - when(restTemplate.exchange( - anyString(), - eq(HttpMethod.GET), - any(HttpEntity.class), - eq(new ParameterizedTypeReference>() {}) - )).thenReturn(responseEntity); - - // Act - boolean result = projectsInfoService.validateConnection(); + void configureApiClient_whenPostConstructCalled_thenBasePathIsConfigured() { + //given + String expectedBaseUrl = "http://localhost:8080"; + ReflectionTestUtils.setField(projectsInfoService, "baseUrl", expectedBaseUrl); - // Assert - assertTrue(result); + //when + projectsInfoService.configureApiClient(); - // Verify the correct URL was called - verify(restTemplate).exchange( - urlCaptor.capture(), - eq(HttpMethod.GET), - httpEntityCaptor.capture(), - eq(new ParameterizedTypeReference>() {}) - ); - - String capturedUrl = urlCaptor.getValue(); - assertEquals(BASE_URL + "/actuator/health", capturedUrl); + //then + verify(apiClient).setBasePath(expectedBaseUrl); } @Test - void testValidateConnection_Non2xxResponse() { - // Arrange - ResponseEntity> responseEntity = new ResponseEntity<>(HttpStatus.SERVICE_UNAVAILABLE); + void getProjectPlatforms_whenCalled_thenPlatformsMapped() throws Exception { + //given + String projectKey = "PROJ"; - when(restTemplate.exchange( - anyString(), - eq(HttpMethod.GET), - any(HttpEntity.class), - eq(new ParameterizedTypeReference>() {}) - )).thenReturn(responseEntity); + var auth = mock(HttpBearerAuth.class); + when(apiClient.getAuthentication("bearerAuth")).thenReturn(auth); - // Act - boolean result = projectsInfoService.validateConnection(); + var apiResponse = new ProjectPlatforms(); + when(projectsApi.getProjectPlatforms(projectKey)).thenReturn(apiResponse); - // Assert - assertFalse(result); - } + List sections = List.of(); + Platforms mapped = new Platforms(sections); + when(platformsMapper.asPlatforms(apiResponse)).thenReturn(mapped); - @Test - void testValidateConnection_Exception() { - // Arrange - when(restTemplate.exchange( - anyString(), - eq(HttpMethod.GET), - any(HttpEntity.class), - eq(new ParameterizedTypeReference>() {}) - )).thenThrow(new RestClientException("Connection refused")); + //when + Platforms result = projectsInfoService.getProjectPlatforms(projectKey); - // Act - boolean result = projectsInfoService.validateConnection(); + //then + verify(projectsApi).getProjectPlatforms(projectKey); + verify(platformsMapper).asPlatforms(apiResponse); - // Assert - assertFalse(result); + assertThat(result).isSameAs(mapped); } @Test - void testValidateConnection_RuntimeException() { - // Arrange - when(restTemplate.exchange( - anyString(), - eq(HttpMethod.GET), - any(HttpEntity.class), - eq(new ParameterizedTypeReference>() {}) - )).thenThrow(new RuntimeException("Unexpected error")); + void validateConnection_whenCalled_thenReturnTrue() { + //given - // Act + //when boolean result = projectsInfoService.validateConnection(); - // Assert - assertFalse(result); - } - - // ========== Tests for isHealthy ========== - - @Test - void testIsHealthy_Success() { - // Arrange - Map healthResponse = Map.of("status", "UP"); - ResponseEntity> responseEntity = new ResponseEntity<>(healthResponse, HttpStatus.OK); - - when(restTemplate.exchange( - anyString(), - eq(HttpMethod.GET), - any(HttpEntity.class), - eq(new ParameterizedTypeReference>() {}) - )).thenReturn(responseEntity); - - // Act - boolean result = projectsInfoService.isHealthy(); - - // Assert - assertTrue(result); - } - - @Test - void testIsHealthy_Failure() { - // Arrange - ResponseEntity> responseEntity = new ResponseEntity<>(HttpStatus.SERVICE_UNAVAILABLE); - - when(restTemplate.exchange( - anyString(), - eq(HttpMethod.GET), - any(HttpEntity.class), - eq(new ParameterizedTypeReference>() {}) - )).thenReturn(responseEntity); - - // Act - boolean result = projectsInfoService.isHealthy(); - - // Assert - assertFalse(result); - } - - @Test - void testIsHealthy_Exception() { - // Arrange - when(restTemplate.exchange( - anyString(), - eq(HttpMethod.GET), - any(HttpEntity.class), - eq(new ParameterizedTypeReference>() {}) - )).thenThrow(new RestClientException("Connection timeout")); - - // Act - boolean result = projectsInfoService.isHealthy(); - - // Assert - assertFalse(result); + //then + assertThat(result).isTrue(); } @Test - void testIsHealthy_RuntimeException() { - // Arrange - when(restTemplate.exchange( - anyString(), - eq(HttpMethod.GET), - any(HttpEntity.class), - eq(new ParameterizedTypeReference>() {}) - )).thenThrow(new RuntimeException("Unexpected error")); + void isHealthy_whenCalled_thenReturnTrue() { + //given - // Act + //when boolean result = projectsInfoService.isHealthy(); - // Assert - assertFalse(result); - } - - // ========== Helper methods ========== - - private Map createValidProjectPlatformsResponse() { - Map response = new HashMap<>(); - response.put("disabledPlatforms", java.util.List.of("platform1", "platform2")); - - Map platformLinks = new HashMap<>(); - platformLinks.put("jenkins", "https://jenkins.example.com/job/test-project"); - platformLinks.put("sonar", "https://sonar.example.com/dashboard?id=test-project"); - response.put("platformLinks", platformLinks); - - return response; - } - - private Map createComplexProjectPlatformsResponse() { - Map response = new HashMap<>(); - response.put("disabledPlatforms", java.util.List.of("platform1")); - - Map platformLinks = new HashMap<>(); - platformLinks.put("jenkins", "https://jenkins.example.com/job/test-project"); - response.put("platformLinks", platformLinks); - - // Add sections - Map section1 = new HashMap<>(); - section1.put("section", "CI/CD"); - - Map link1 = new HashMap<>(); - link1.put("name", "Build Pipeline"); - link1.put("url", "https://jenkins.example.com/job/build"); - section1.put("links", java.util.List.of(link1)); - - response.put("sections", java.util.List.of(section1)); - - return response; + //then + assertThat(result).isTrue(); } -} +} \ No newline at end of file diff --git a/external-service-projects-info-service/static/images/application-configuration.png b/external-service-projects-info-service/static/images/application-configuration.png deleted file mode 100644 index 6b3bbe9..0000000 Binary files a/external-service-projects-info-service/static/images/application-configuration.png and /dev/null differ diff --git a/external-service-projects-info-service/static/images/configuration-tree.png b/external-service-projects-info-service/static/images/configuration-tree.png deleted file mode 100644 index 1dd1709..0000000 Binary files a/external-service-projects-info-service/static/images/configuration-tree.png and /dev/null differ