diff --git a/.github/workflows/release_onboarding_sdk.yml b/.github/workflows/release_onboarding_sdk.yml index 79b8ec495..81befd061 100644 --- a/.github/workflows/release_onboarding_sdk.yml +++ b/.github/workflows/release_onboarding_sdk.yml @@ -1,24 +1,15 @@ name: Release onboarding sdk on: - pull_request: - branches: - - develop - types: [ closed ] - paths: - - 'onboarding-sdk/**/src/**' - workflow_dispatch: permissions: - packages: write contents: write jobs: setup: name: Release runs-on: ubuntu-latest - if: github.event.pull_request.merged == true steps: - uses: actions/checkout@v3 with: @@ -31,28 +22,6 @@ jobs: java-version: '17' cache: maven - - name: Set patch - run: | - mvn build-helper:parse-version versions:set -pl onboarding-sdk -DnewVersion='${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.nextIncrementalVersion}' - mvn versions:commit -f onboarding-sdk/pom.xml - shell: bash - - - name: Get Version - run: | - echo "VERSION=$(mvn -pl onboarding-sdk help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV - echo "🆙 Bump Version to ${VERSION}" - shell: bash - - - name: Push New Version - shell: bash - run: | - echo "${VERSION}" - git ls-files ./onboarding-sdk | grep 'pom.xml' | xargs git add - git config --global user.email "selfcare-github-bot@pagopa.it" - git config --global user.name "selfcare-github-bot" - git commit -m "Bump onboarding SDK to version ${VERSION}" || exit 0 - git push origin ${{ github.ref_name}} - - name: Build with Maven run: mvn -B package -f onboarding-sdk/pom.xml shell: bash diff --git a/onboarding-sdk/onboarding-sdk-azure-storage/pom.xml b/onboarding-sdk/onboarding-sdk-azure-storage/pom.xml index 31b8dc22b..ad9715b8b 100644 --- a/onboarding-sdk/onboarding-sdk-azure-storage/pom.xml +++ b/onboarding-sdk/onboarding-sdk-azure-storage/pom.xml @@ -6,14 +6,12 @@ it.pagopa.selfcare onboarding-sdk - 0.0.4 + 0.1.0 onboarding-sdk-azure-storage - 11 - 11 UTF-8 12.24.0 diff --git a/onboarding-sdk/onboarding-sdk-common/pom.xml b/onboarding-sdk/onboarding-sdk-common/pom.xml index 55836d50d..90dffcbe0 100644 --- a/onboarding-sdk/onboarding-sdk-common/pom.xml +++ b/onboarding-sdk/onboarding-sdk-common/pom.xml @@ -4,7 +4,7 @@ it.pagopa.selfcare onboarding-sdk - 0.0.4 + 0.1.0 onboarding-sdk-common onboarding-sdk-common diff --git a/onboarding-sdk/onboarding-sdk-product/README.md b/onboarding-sdk/onboarding-sdk-product/README.md index 049f28831..e11be895b 100644 --- a/onboarding-sdk/onboarding-sdk-product/README.md +++ b/onboarding-sdk/onboarding-sdk-product/README.md @@ -2,6 +2,10 @@ This library has been developed to provide a set of Java utility classes to simplify the work of handle **Selfcare Product** as string. +Selfcare Products is a collection of PagoPA products available for use by institutions. Each product contains specific information, such as its status, admitted role, or a filepath template for building contract necessary for selfcare business logic. + +The Onboarding SDK Product offers a set of classes designed for managing this collection of records using a file. + ## Installation To use this library in your projects, you can add the dependency to your pom.xml if you're using Maven: @@ -23,117 +27,7 @@ dependencies { ``` ## Product JSON Schema -Product string which are used by ProductService must follow a specific schema: - -``` -{ - "type" : "record", - "name" : "Product", - "namespace" : "it.pagopa.selfcare.product.entity", - "fields" : [ { - "name" : "id", - "type" : [ "string" ] - }, { - "name" : "logo", - "type" : [ "string" ] - }, { - "name" : "depictImageUrl", - "type" : [ "string" ] - }, { - "name" : "title", - "type" : [ "string" ] - }, { - "name" : "logoBgColor", - "type" : [ "string" ] - }, { - "name" : "description", - "type" : [ "string" ] - }, { - "name" : "urlPublic", - "type" : [ "string" ] - }, { - "name" : "urlBO", - "type" : [ "string" ] - }, { - "name" : "createdAt", - "type" : [ "string" ] - }, { - "name" : "createdBy", - "type" : [ "string" ] - }, { - "name" : "modifiedAt", - "type" : [ "string" ] - }, { - "name" : "modifiedBy", - "type" : [ "string" ] - }, { - "name" : "roleManagementURL", - "type" : [ "null", "string" ] - }, { - "name" : "contractTemplateUpdatedAt", - "type" : [ "string" ] - }, { - "name" : "contractTemplatePath", - "type" : [ "string" ] - }, { - "name" : "contractTemplateVersion", - "type" : [ "string" ] - }, { - "name" : "institutionContractMappings", - "type" : [ "null", { - "type" : "map", - "values" : { - "type" : "record", - "name" : "ContractStorage", - "fields" : [ { - "name" : "contractTemplateUpdatedAt", - "type" : [ "null", "string" ] - }, { - "name" : "contractTemplatePath", - "type" : [ "null", "string" ] - }, { - "name" : "contractTemplateVersion", - "type" : [ "null", "string" ] - } ] - } - } ] - }, { - "name" : "enabled", - "type" : "boolean" - }, { - "name" : "delegable", - "type" : ["null","boolean"] - }, { - "name" : "status", - "type" : [ "string" ] - }, { - "name" : "parentId", - "type" : [ "null", "string" ] - }, { - "name" : "identityTokenAudience", - "type" : [ "string" ] - }, { - "name" : "backOfficeEnvironmentConfigurations", - "type" : [ "null", { - "type" : "map", - "values" : { - "type" : "record", - "name" : "BackOfficeConfigurations", - "fields" : [ { - "name" : "url", - "type" : [ "null", "string" ] - }, { - "name" : "identityTokenAudience", - "type" : [ "null", "string" ] - } ] - } - } ] - }, { - "name" : "parent", - "type" : [ "null", "Product" ] - } ] -} -``` +Product string which are used by ProductService must follow a specific schema, look at src/main/schema folder. ## Usage @@ -151,4 +45,19 @@ public class Main { final Product product = productService.getProductIsValid(productId); } } -``` \ No newline at end of file +``` + +Generally, you load product json string from an azure storage container, this example use onboading-sdk-azure-storage, and inject the product service in the context of Quarkus or Spring (replace @ApplicationScoped with @Bean). + +```java script + @ApplicationScoped + public ProductService productService(AzureStorageConfig azureStorageConfig){ + AzureBlobClient azureBlobClient = new AzureBlobClientDefault(azureStorageConfig.connectionStringProduct(), azureStorageConfig.containerProduct()); + String productJsonString = azureBlobClient.getFileAsText(azureStorageConfig.productFilepath()); + try { + return new ProductServiceDefault(productJsonString, objectMapper()); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("Found an issue when trying to serialize product json string!!"); + } + } + ``` \ No newline at end of file diff --git a/onboarding-sdk/onboarding-sdk-product/pom.xml b/onboarding-sdk/onboarding-sdk-product/pom.xml index c9414ffed..4fec37a42 100644 --- a/onboarding-sdk/onboarding-sdk-product/pom.xml +++ b/onboarding-sdk/onboarding-sdk-product/pom.xml @@ -4,13 +4,14 @@ it.pagopa.selfcare onboarding-sdk - 0.0.4 + 0.1.0 onboarding-sdk-product onboarding-sdk-product 2.15.2 + 5.7.2 @@ -25,7 +26,20 @@ it.pagopa.selfcare onboarding-sdk-common - 0.0.1 + ${parent.version} + + + + org.junit.jupiter + junit-jupiter-api + ${junit-jupiter.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter.version} + test diff --git a/onboarding-sdk/onboarding-sdk-product/src/main/java/it/pagopa/selfcare/product/service/ProductService.java b/onboarding-sdk/onboarding-sdk-product/src/main/java/it/pagopa/selfcare/product/service/ProductService.java index d25b130a9..56ead812b 100644 --- a/onboarding-sdk/onboarding-sdk-product/src/main/java/it/pagopa/selfcare/product/service/ProductService.java +++ b/onboarding-sdk/onboarding-sdk-product/src/main/java/it/pagopa/selfcare/product/service/ProductService.java @@ -10,11 +10,13 @@ import java.util.Map; public interface ProductService { - List getProducts(boolean rootOnly); + public List getProducts(boolean rootOnly, boolean valid) ; void validateRoleMappings(Map roleMappings); - Product getProduct(String id, InstitutionType institutionType); + Product getProduct(String productId); - Product getProductIsValid(String id); + void fillContractTemplatePathAndVersion(Product product, InstitutionType institutionType); + + Product getProductIsValid(String productId); } diff --git a/onboarding-sdk/onboarding-sdk-product/src/main/java/it/pagopa/selfcare/product/service/ProductServiceDefault.java b/onboarding-sdk/onboarding-sdk-product/src/main/java/it/pagopa/selfcare/product/service/ProductServiceDefault.java index 320c0ee16..add7cd8b3 100644 --- a/onboarding-sdk/onboarding-sdk-product/src/main/java/it/pagopa/selfcare/product/service/ProductServiceDefault.java +++ b/onboarding-sdk/onboarding-sdk-product/src/main/java/it/pagopa/selfcare/product/service/ProductServiceDefault.java @@ -31,33 +31,45 @@ public ProductServiceDefault(String productString) throws JsonProcessingExceptio mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); - List productList = mapper.readValue(productString, new TypeReference>(){}); - if(Objects.isNull(productList) || productList.isEmpty()) throw new ProductNotFoundException("json string is empty!"); - this.productsMap = productList.stream() - .collect(Collectors.toMap(Product::getId, Function.identity())); + this.productsMap = constructProductsMap(productString, mapper); } public ProductServiceDefault(String productString, ObjectMapper mapper) throws JsonProcessingException { + this.productsMap = constructProductsMap(productString, mapper); + } + private Map constructProductsMap(String productString, ObjectMapper mapper) throws JsonProcessingException { List productList = mapper.readValue(productString, new TypeReference>(){}); if(Objects.isNull(productList) || productList.isEmpty()) throw new ProductNotFoundException("json string is empty!"); - this.productsMap = productList.stream() + return productList.stream() .collect(Collectors.toMap(Product::getId, Function.identity())); } + /** + * Returns the list of PagoPA products tree which are not INACTIVE + * @param rootOnly if true only product that has parent is null are returned + * @return List of PagoPA products + */ @Override - public List getProducts(boolean rootOnly) { + public List getProducts(boolean rootOnly, boolean valid) { return rootOnly ? productsMap.values().stream() - .filter(product -> Objects.nonNull(product.getParentId())) + .filter(product -> Objects.isNull(product.getParentId())) + .filter(product -> !valid || !statusIsNotValid(product.getStatus())) .collect(Collectors.toList()) - : productsMap.values().stream() - .filter(product -> !ProductStatus.INACTIVE.equals(product.getStatus())) + : productsMap.values().stream() + .filter(product -> !valid || !statusIsNotValid(product.getStatus())) .collect(Collectors.toList()); } - + /** + * Utility method for validating role mappings that contains associations between Selfcare role and Product role. + * Each Selfcare role must be only one Product role except OPERATOR. + * @param roleMappings + * @throws IllegalArgumentException roleMappings is null or empty + * @throws InvalidRoleMappingException Selfcare role have more than one Product role + */ @Override public void validateRoleMappings(Map roleMappings) { @@ -76,42 +88,74 @@ public void validateRoleMappings(Map roleM }); } - + /** + * Return a product by productId without any filter + * retrieving data from institutionContractMappings map + * @param productId + * @return Product + * @throws IllegalArgumentException if @param id is null + * @throws ProductNotFoundException if product is not found + * + */ @Override - public Product getProduct(String id, InstitutionType institutionType) { + public Product getProduct(String productId) { + return getProduct(productId, false); + } + + private Product getProduct(String productId, boolean filterValid) { - if(Objects.isNull(id)) - throw new IllegalArgumentException(REQUIRED_PRODUCT_ID_MESSAGE); - Product foundProduct = Optional.ofNullable(productsMap.get(id)).orElseThrow(ProductNotFoundException::new); - if (foundProduct.getStatus() == ProductStatus.INACTIVE) { + if(Objects.isNull(productId)) { + throw new IllegalArgumentException(REQUIRED_PRODUCT_ID_MESSAGE); + } + Product product = Optional.ofNullable(productsMap.get(productId)) + .orElseThrow(ProductNotFoundException::new); + if (filterValid && statusIsNotValid(product.getStatus())) { throw new ProductNotFoundException(); } - if (institutionType != null && foundProduct.getInstitutionContractMappings() != null && foundProduct.getInstitutionContractMappings().containsKey(institutionType)) { - foundProduct.setContractTemplatePath(foundProduct.getInstitutionContractMappings().get(institutionType).getContractTemplatePath()); - foundProduct.setContractTemplateVersion(foundProduct.getInstitutionContractMappings().get(institutionType).getContractTemplateVersion()); + + if (product.getParentId() != null) { + Product parent = Optional.ofNullable(productsMap.get(product.getParentId())) + .orElseThrow(ProductNotFoundException::new); + if (filterValid && statusIsNotValid(parent.getStatus())) { + throw new ProductNotFoundException(); + } + + product.setParent(parent); } - return foundProduct; + + return product; } + /** + * Fills contractTemplatePath and ContractTemplateVersion based on @param institutionType. + * If institutionContractMappings contains institutionType, it take value from that setting inside + * contractTemplatePath and contractTemplateVersion of product + * @param product Product + * @param institutionType InstitutionType + */ @Override - public Product getProductIsValid(String id) { - if(Objects.isNull(id)) - throw new IllegalArgumentException(REQUIRED_PRODUCT_ID_MESSAGE); - Product foundProduct = Optional.ofNullable(productsMap.get(id)).orElseThrow(ProductNotFoundException::new); - Product baseProduct = null; - if (foundProduct.getParentId() != null) { - baseProduct = Optional.ofNullable(productsMap.get(id)).orElseThrow(ProductNotFoundException::new); - if (baseProduct.getStatus() == ProductStatus.PHASE_OUT) { - return null; - } else if (foundProduct.getStatus() != ProductStatus.PHASE_OUT){ - foundProduct.setParent(baseProduct); - return foundProduct; - } - } else if (foundProduct.getStatus() != ProductStatus.PHASE_OUT) { - return foundProduct; + public void fillContractTemplatePathAndVersion(Product product, InstitutionType institutionType) { + if (institutionType != null && product.getInstitutionContractMappings() != null && product.getInstitutionContractMappings().containsKey(institutionType)) { + product.setContractTemplatePath(product.getInstitutionContractMappings().get(institutionType).getContractTemplatePath()); + product.setContractTemplateVersion(product.getInstitutionContractMappings().get(institutionType).getContractTemplateVersion()); } + } + + + /** + * Returns the information for a single product if it has not PHASE_OUT,INACTIVE status and its parent has not PHASE_OUT,INACTIVE status + * @param productId + * @return Product if it is valid or null if it has PHASE_OUT,INACTIVE status + * @throws IllegalArgumentException product id is null + * @throws ProductNotFoundException product id or its parent does not exist or have PHASE_OUT,INACTIVE status + */ + @Override + public Product getProductIsValid(String productId) { + return getProduct(productId, true); + } - return null; + private static boolean statusIsNotValid(ProductStatus status) { + return List.of(ProductStatus.INACTIVE, ProductStatus.PHASE_OUT).contains(status); } diff --git a/onboarding-sdk/onboarding-sdk-product/src/test/java/ProductServiceDefaultTest.java b/onboarding-sdk/onboarding-sdk-product/src/test/java/ProductServiceDefaultTest.java new file mode 100644 index 000000000..84438221a --- /dev/null +++ b/onboarding-sdk/onboarding-sdk-product/src/test/java/ProductServiceDefaultTest.java @@ -0,0 +1,116 @@ +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import it.pagopa.selfcare.onboarding.common.PartyRole; +import it.pagopa.selfcare.product.entity.ProductRole; +import it.pagopa.selfcare.product.entity.ProductRoleInfo; +import it.pagopa.selfcare.product.exception.InvalidRoleMappingException; +import it.pagopa.selfcare.product.exception.ProductNotFoundException; +import it.pagopa.selfcare.product.service.ProductServiceDefault; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +public class ProductServiceDefaultTest { + + final private String PRODUCT_JSON_STRING_EMPTY = "[]"; + final private String PRODUCT_JSON_STRING = "[{\"id\":\"prod-test-parent\",\"status\":\"ACTIVE\"}," + + "{\"id\":\"prod-test\", \"parentId\":\"prod-test-parent\",\"status\":\"ACTIVE\"}," + + "{\"id\":\"prod-inactive\",\"status\":\"INACTIVE\"}]"; + + @Test + void productServiceDefault_shouldThrowProductNotFoundExceptionIfJsonIsEmpty() { + assertThrows(ProductNotFoundException.class, () -> new ProductServiceDefault(PRODUCT_JSON_STRING_EMPTY)); + } + @Test + void productServiceDefault_shouldNotThrowProductNotFoundExceptionIfJsonIsValid() { + assertDoesNotThrow(() -> new ProductServiceDefault(PRODUCT_JSON_STRING)); + } + @Test + void productServiceDefault_shouldThrowProductNotFoundExceptionIfJsonIsEmptyAndMapper() { + assertThrows(ProductNotFoundException.class, () -> new ProductServiceDefault(PRODUCT_JSON_STRING_EMPTY, new ObjectMapper())); + } + + @Test + void getProducts_shouldReturnProductsRootOnly() throws JsonProcessingException { + ProductServiceDefault productService = new ProductServiceDefault(PRODUCT_JSON_STRING); + assertEquals( 2, productService.getProducts(true, false).size()); + } + + @Test + void getProducts_shouldReturnProductsAll() throws JsonProcessingException { + ProductServiceDefault productService = new ProductServiceDefault(PRODUCT_JSON_STRING); + assertEquals(3, productService.getProducts(false, false).size()); + } + + @Test + void getProducts_shouldReturnProductsRootOnlyAndValid() throws JsonProcessingException { + ProductServiceDefault productService = new ProductServiceDefault(PRODUCT_JSON_STRING); + assertEquals( 1, productService.getProducts(true, true).size()); + } + + @Test + void getProducts_shouldReturnProductsAllAndValid() throws JsonProcessingException { + ProductServiceDefault productService = new ProductServiceDefault(PRODUCT_JSON_STRING); + assertEquals(2, productService.getProducts(false, true).size()); + } + + @Test + void validateRoleMappings_shouldThrowIllegalExceptionIfRoleMappingIsNull() throws JsonProcessingException { + ProductServiceDefault productService = new ProductServiceDefault(PRODUCT_JSON_STRING); + assertThrows(IllegalArgumentException.class, () -> productService.validateRoleMappings(new HashMap<>())); + } + + @Test + void validateRoleMappings_shouldThrowIllegalExceptionIfProductRoleInfoIsNull() throws JsonProcessingException { + ProductServiceDefault productService = new ProductServiceDefault(PRODUCT_JSON_STRING); + Map roleMappings = new HashMap<>(); + roleMappings.put(PartyRole.MANAGER, null); + assertThrows(IllegalArgumentException.class, () -> productService.validateRoleMappings(roleMappings)); + } + + @Test + void validateRoleMappings_shouldThrowIllegalExceptionIfProductRoleInfoIsRolesEmpty() throws JsonProcessingException { + ProductServiceDefault productService = new ProductServiceDefault(PRODUCT_JSON_STRING); + Map roleMappings = new HashMap<>(); + roleMappings.put(PartyRole.MANAGER, new ProductRoleInfo()); + assertThrows(IllegalArgumentException.class, () -> productService.validateRoleMappings(roleMappings)); + } + + @Test + void validateRoleMappings_shouldThrowInvalidRoleMappingExceptionIfProductRoleInfoIsRolesEmpty() throws JsonProcessingException { + ProductServiceDefault productService = new ProductServiceDefault(PRODUCT_JSON_STRING); + Map roleMappings = new HashMap<>(); + ProductRoleInfo productRoleInfo1 = new ProductRoleInfo(); + productRoleInfo1.setRoles(List.of(new ProductRole(), new ProductRole())); + roleMappings.put(PartyRole.MANAGER, productRoleInfo1); + assertThrows(InvalidRoleMappingException.class, () -> productService.validateRoleMappings(roleMappings)); + } + + @Test + void getProduct_shouldThrowIllegalExceptionIfIdNull() throws JsonProcessingException { + ProductServiceDefault productService = new ProductServiceDefault(PRODUCT_JSON_STRING); + assertThrows(IllegalArgumentException.class, () -> productService.getProduct(null)); + } + + @Test + void getProduct_shouldThrowProductNotFoundExceptionIfIdNotFound() throws JsonProcessingException { + ProductServiceDefault productService = new ProductServiceDefault(PRODUCT_JSON_STRING); + assertThrows(ProductNotFoundException.class, () -> productService.getProduct("prod-not-found")); + } + + @Test + void getProduct_shouldGetProduct() throws JsonProcessingException { + ProductServiceDefault productService = new ProductServiceDefault(PRODUCT_JSON_STRING); + assertNotNull(productService.getProduct("prod-test")); + } + + @Test + void getProductValid_shouldThrowProductNotFoundExceptionIfProductInactive() throws JsonProcessingException { + ProductServiceDefault productService = new ProductServiceDefault(PRODUCT_JSON_STRING); + assertThrows(ProductNotFoundException.class, () -> productService.getProductIsValid("prod-inactive")); + } +} diff --git a/onboarding-sdk/pom.xml b/onboarding-sdk/pom.xml index cdd130312..6aa9c4584 100644 --- a/onboarding-sdk/pom.xml +++ b/onboarding-sdk/pom.xml @@ -10,11 +10,11 @@ onboarding-sdk pom onboarding-sdk - 0.0.4 + 0.1.0 - 11 - 11 + 17 + 17 onboarding-sdk-product