From 20866625d1e4268dc2c4f5091953689093fa735f Mon Sep 17 00:00:00 2001 From: Andrej Petras Date: Thu, 16 Nov 2023 19:32:58 +0100 Subject: [PATCH] feat: migration (#1) * feat: migration, add operator rest api * feat: migration, add liquibase and product operator rest api impl * feat: migration, update tests * feat: migration, add openapi def * feat: migration, add product search impl * feat: migration, update api * feat: migration, fix the exception handling * feat: migration, external api add method to get product by name * feat: migration, add tests * feat: migration, add tests for internal rest api --- .github/changelog.yaml | 13 + .github/dependabot.yml | 8 + .github/workflows/build-branch.yml | 13 + .github/workflows/build-pr.yml | 9 + .github/workflows/build-release.yml | 9 + .github/workflows/build.yml | 21 + .github/workflows/create-fix-branch.yml | 7 + .github/workflows/create-release.yml | 7 + .github/workflows/documentation.yml | 10 + .github/workflows/sonar-pr.yml | 12 + .gitignore | 43 ++ pom.xml | 213 ++++++ src/main/docker/Dockerfile.jvm | 13 + src/main/docker/Dockerfile.native | 11 + src/main/helm/Chart.yaml | 17 + src/main/helm/values.yaml | 6 + .../product/store/domain/CommonLogParam.java | 22 + .../criteria/MicrofrontendSearchCriteria.java | 19 + .../criteria/ProductSearchCriteria.java | 17 + .../store/domain/daos/MicrofrontendDAO.java | 75 +++ .../product/store/domain/daos/ProductDAO.java | 57 ++ .../store/domain/models/Microfrontend.java | 79 +++ .../product/store/domain/models/Product.java | 34 + .../controllers/ProductsRestControllerV1.java | 65 ++ .../v1/mappers/ExceptionMapperV1.java | 56 ++ .../external/v1/mappers/ProductMapperV1.java | 39 ++ .../MicrofrontendsInternalRestController.java | 98 +++ .../ProductsInternalRestController.java | 95 +++ .../mappers/InternalExceptionMapper.java | 64 ++ .../internal/mappers/MicrofrontendMapper.java | 45 ++ .../rs/internal/mappers/ProductMapper.java | 45 ++ .../OperatorMfeRestControllerV1.java | 61 ++ .../mappers/OperatorMfeExceptionMapperV1.java | 65 ++ .../mfe/v1/mappers/OperatorMfeMapperV1.java | 39 ++ .../OperatorProductRestControllerV1.java | 65 ++ .../OperatorProductExceptionMapperV1.java | 64 ++ .../v1/mappers/OperatorProductMapperV1.java | 37 ++ .../openapi/onecx-product-store-internal.yaml | 607 ++++++++++++++++++ .../onecx-product-store-operator-mfe-v1.yaml | 117 ++++ ...ecx-product-store-operator-product-v1.yaml | 80 +++ src/main/openapi/onecx-product-store-v1.yaml | 168 +++++ src/main/resources/application.properties | 22 + src/main/resources/db/changeLog.xml | 8 + .../db/v1/2023-11-13-create-tables.xml | 70 ++ .../onecx/product/store/AbstractTest.java | 24 + .../domain/daos/MicrofrontendDAOTest.java | 43 ++ .../store/domain/daos/ProductDAOTest.java | 41 ++ ...ProductsRestControllerV1ExceptionTest.java | 52 ++ .../ProductsRestControllerV1IT.java | 7 + .../ProductsRestControllerV1Test.java | 83 +++ ...icrofrontendsInternalRestControllerIT.java | 7 + ...rofrontendsInternalRestControllerTest.java | 259 ++++++++ .../ProductsInternalRestControllerIT.java | 7 + .../ProductsInternalRestControllerTest.java | 240 +++++++ ...ratorMfeRestControllerV1ExceptionTest.java | 64 ++ .../OperatorMfeRestControllerV1IT.java | 8 + .../OperatorMfeRestControllerV1Test.java | 113 ++++ ...rProductRestControllerV1ExceptionTest.java | 57 ++ .../OperatorProductRestControllerV1IT.java | 8 + .../OperatorProductRestControllerV1Test.java | 80 +++ src/test/resources/data/testdata-internal.xml | 13 + .../resources/data/testdata-operator-mfe.xml | 11 + .../data/testdata-operator-product.xml | 5 + src/test/resources/data/testdata-v1.xml | 9 + 64 files changed, 3756 insertions(+) create mode 100644 .github/changelog.yaml create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/build-branch.yml create mode 100644 .github/workflows/build-pr.yml create mode 100644 .github/workflows/build-release.yml create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/create-fix-branch.yml create mode 100644 .github/workflows/create-release.yml create mode 100644 .github/workflows/documentation.yml create mode 100644 .github/workflows/sonar-pr.yml create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/docker/Dockerfile.jvm create mode 100644 src/main/docker/Dockerfile.native create mode 100644 src/main/helm/Chart.yaml create mode 100644 src/main/helm/values.yaml create mode 100644 src/main/java/io/github/onecx/product/store/domain/CommonLogParam.java create mode 100644 src/main/java/io/github/onecx/product/store/domain/criteria/MicrofrontendSearchCriteria.java create mode 100644 src/main/java/io/github/onecx/product/store/domain/criteria/ProductSearchCriteria.java create mode 100644 src/main/java/io/github/onecx/product/store/domain/daos/MicrofrontendDAO.java create mode 100644 src/main/java/io/github/onecx/product/store/domain/daos/ProductDAO.java create mode 100644 src/main/java/io/github/onecx/product/store/domain/models/Microfrontend.java create mode 100644 src/main/java/io/github/onecx/product/store/domain/models/Product.java create mode 100644 src/main/java/io/github/onecx/product/store/rs/external/v1/controllers/ProductsRestControllerV1.java create mode 100644 src/main/java/io/github/onecx/product/store/rs/external/v1/mappers/ExceptionMapperV1.java create mode 100644 src/main/java/io/github/onecx/product/store/rs/external/v1/mappers/ProductMapperV1.java create mode 100644 src/main/java/io/github/onecx/product/store/rs/internal/controllers/MicrofrontendsInternalRestController.java create mode 100644 src/main/java/io/github/onecx/product/store/rs/internal/controllers/ProductsInternalRestController.java create mode 100644 src/main/java/io/github/onecx/product/store/rs/internal/mappers/InternalExceptionMapper.java create mode 100644 src/main/java/io/github/onecx/product/store/rs/internal/mappers/MicrofrontendMapper.java create mode 100644 src/main/java/io/github/onecx/product/store/rs/internal/mappers/ProductMapper.java create mode 100644 src/main/java/io/github/onecx/product/store/rs/operator/mfe/v1/controllers/OperatorMfeRestControllerV1.java create mode 100644 src/main/java/io/github/onecx/product/store/rs/operator/mfe/v1/mappers/OperatorMfeExceptionMapperV1.java create mode 100644 src/main/java/io/github/onecx/product/store/rs/operator/mfe/v1/mappers/OperatorMfeMapperV1.java create mode 100644 src/main/java/io/github/onecx/product/store/rs/operator/product/v1/controllers/OperatorProductRestControllerV1.java create mode 100644 src/main/java/io/github/onecx/product/store/rs/operator/product/v1/mappers/OperatorProductExceptionMapperV1.java create mode 100644 src/main/java/io/github/onecx/product/store/rs/operator/product/v1/mappers/OperatorProductMapperV1.java create mode 100644 src/main/openapi/onecx-product-store-internal.yaml create mode 100644 src/main/openapi/onecx-product-store-operator-mfe-v1.yaml create mode 100644 src/main/openapi/onecx-product-store-operator-product-v1.yaml create mode 100644 src/main/openapi/onecx-product-store-v1.yaml create mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/db/changeLog.xml create mode 100644 src/main/resources/db/v1/2023-11-13-create-tables.xml create mode 100644 src/test/java/io/github/onecx/product/store/AbstractTest.java create mode 100644 src/test/java/io/github/onecx/product/store/domain/daos/MicrofrontendDAOTest.java create mode 100644 src/test/java/io/github/onecx/product/store/domain/daos/ProductDAOTest.java create mode 100644 src/test/java/io/github/onecx/product/store/rs/external/v1/controllers/ProductsRestControllerV1ExceptionTest.java create mode 100644 src/test/java/io/github/onecx/product/store/rs/external/v1/controllers/ProductsRestControllerV1IT.java create mode 100644 src/test/java/io/github/onecx/product/store/rs/external/v1/controllers/ProductsRestControllerV1Test.java create mode 100644 src/test/java/io/github/onecx/product/store/rs/internal/controllers/MicrofrontendsInternalRestControllerIT.java create mode 100644 src/test/java/io/github/onecx/product/store/rs/internal/controllers/MicrofrontendsInternalRestControllerTest.java create mode 100644 src/test/java/io/github/onecx/product/store/rs/internal/controllers/ProductsInternalRestControllerIT.java create mode 100644 src/test/java/io/github/onecx/product/store/rs/internal/controllers/ProductsInternalRestControllerTest.java create mode 100644 src/test/java/io/github/onecx/product/store/rs/operator/mfe/v1/controllers/OperatorMfeRestControllerV1ExceptionTest.java create mode 100644 src/test/java/io/github/onecx/product/store/rs/operator/mfe/v1/controllers/OperatorMfeRestControllerV1IT.java create mode 100644 src/test/java/io/github/onecx/product/store/rs/operator/mfe/v1/controllers/OperatorMfeRestControllerV1Test.java create mode 100644 src/test/java/io/github/onecx/product/store/rs/operator/product/v1/controllers/OperatorProductRestControllerV1ExceptionTest.java create mode 100644 src/test/java/io/github/onecx/product/store/rs/operator/product/v1/controllers/OperatorProductRestControllerV1IT.java create mode 100644 src/test/java/io/github/onecx/product/store/rs/operator/product/v1/controllers/OperatorProductRestControllerV1Test.java create mode 100644 src/test/resources/data/testdata-internal.xml create mode 100644 src/test/resources/data/testdata-operator-mfe.xml create mode 100644 src/test/resources/data/testdata-operator-product.xml create mode 100644 src/test/resources/data/testdata-v1.xml diff --git a/.github/changelog.yaml b/.github/changelog.yaml new file mode 100644 index 0000000..0a5526e --- /dev/null +++ b/.github/changelog.yaml @@ -0,0 +1,13 @@ +sections: + - title: Major changes + labels: + - "release/super-feature" + - title: Complete changelog + labels: + - "bug" + - "enhancement" + - "dependencies" +template: | + {{ range $section := .Sections }}{{ if $section.Items }}### {{ $section.GetTitle }}{{ range $item := $section.Items }} + * [#{{ $item.GetID }}]({{ $item.GetURL }}) - {{ $item.GetTitle }}{{ end }}{{ end }} + {{ end }} \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..64412da --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: maven + directory: "/" + schedule: + interval: daily + labels: + - dependencies \ No newline at end of file diff --git a/.github/workflows/build-branch.yml b/.github/workflows/build-branch.yml new file mode 100644 index 0000000..7270213 --- /dev/null +++ b/.github/workflows/build-branch.yml @@ -0,0 +1,13 @@ +name: Build Feature Branch + +on: + push: + branches: + - '**' + - '!main' + - '!fix/[0-9]+.[0-9]+.x' + +jobs: + branch: + uses: onecx/ci-quarkus/.github/workflows/build-branch.yml@v1 + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml new file mode 100644 index 0000000..acff155 --- /dev/null +++ b/.github/workflows/build-pr.yml @@ -0,0 +1,9 @@ +name: Build Pull Request + +on: + pull_request: + +jobs: + pr: + uses: onecx/ci-quarkus/.github/workflows/build-pr.yml@v1 + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml new file mode 100644 index 0000000..b04a59a --- /dev/null +++ b/.github/workflows/build-release.yml @@ -0,0 +1,9 @@ +name: Build Release +on: + push: + tags: + - '**' +jobs: + release: + uses: onecx/ci-quarkus/.github/workflows/build-release.yml@v1 + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..53b0ddd --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,21 @@ +name: Build + +on: + workflow_dispatch: + push: + branches: + - 'main' + - 'fix/[0-9]+.[0-9]+.x' + paths-ignore: + - '.gitignore' + - 'CODEOWNERS' + - 'LICENSE' + - '*.md' + - '*.adoc' + - '*.txt' + - '.all-contributorsrc' + +jobs: + build: + uses: onecx/ci-quarkus/.github/workflows/build.yml@v1 + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/create-fix-branch.yml b/.github/workflows/create-fix-branch.yml new file mode 100644 index 0000000..92af624 --- /dev/null +++ b/.github/workflows/create-fix-branch.yml @@ -0,0 +1,7 @@ +name: Create Fix Branch +on: + workflow_dispatch: +jobs: + fix: + uses: onecx/ci-common/.github/workflows/create-fix-branch.yml@v1 + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml new file mode 100644 index 0000000..c97eb42 --- /dev/null +++ b/.github/workflows/create-release.yml @@ -0,0 +1,7 @@ +name: Create Release Version +on: + workflow_dispatch: +jobs: + release: + uses: onecx/ci-common/.github/workflows/create-release.yml@v1 + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 0000000..e43d7dc --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,10 @@ +name: Update documentation +on: + push: + branches: [ main ] + paths: + - 'docs/**' +jobs: + release: + uses: onecx/ci-common/.github/workflows/documentation.yml@v1 + secrets: inherit diff --git a/.github/workflows/sonar-pr.yml b/.github/workflows/sonar-pr.yml new file mode 100644 index 0000000..02c3e1e --- /dev/null +++ b/.github/workflows/sonar-pr.yml @@ -0,0 +1,12 @@ +name: Sonar Pull Request + +on: + workflow_run: + workflows: ["Build Pull Request"] + types: + - completed + +jobs: + pr: + uses: onecx/ci-quarkus/.github/workflows/quarkus-pr-sonar.yml@v1 + secrets: inherit \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8c7863e --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +#Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +release.properties +.flattened-pom.xml + +# Eclipse +.project +.classpath +.settings/ +bin/ + +# IntelliJ +.idea +*.ipr +*.iml +*.iws + +# NetBeans +nb-configuration.xml + +# Visual Studio Code +.vscode +.factorypath + +# OSX +.DS_Store + +# Vim +*.swp +*.swo + +# patch +*.orig +*.rej + +# Local environment +.env + +# Plugin directory +/.quarkus/cli/plugins/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..deb96b8 --- /dev/null +++ b/pom.xml @@ -0,0 +1,213 @@ + + + + 4.0.0 + + + io.github.onecx + onecx-quarkus3-parent + 0.18.0 + + + onecx-product-store-svc + 999-SNAPSHOT + + + + + org.tkit.quarkus.lib + tkit-quarkus-jpa + + + org.tkit.quarkus.lib + tkit-quarkus-log-cdi + + + org.tkit.quarkus.lib + tkit-quarkus-log-rs + + + org.tkit.quarkus.lib + tkit-quarkus-log-json + + + org.tkit.quarkus.lib + tkit-quarkus-rest + + + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-liquibase + + + com.github.blagerweij + liquibase-sessionlock + + + io.quarkus + quarkus-smallrye-health + + + io.quarkus + quarkus-micrometer-registry-prometheus + + + io.quarkus + quarkus-hibernate-orm + + + io.quarkus + quarkus-resteasy-reactive + + + io.quarkus + quarkus-resteasy-reactive-jackson + + + io.quarkus + quarkus-jdbc-postgresql + + + io.quarkus + quarkus-smallrye-openapi + + + io.quarkus + quarkus-hibernate-validator + + + io.quarkus + quarkus-opentelemetry + + + + + org.projectlombok + lombok + + + org.mapstruct + mapstruct + + + + + io.quarkus + quarkus-junit5 + test + + + io.quarkus + quarkus-junit5-mockito + test + + + io.rest-assured + rest-assured + test + + + org.tkit.quarkus.lib + tkit-quarkus-test-db-import + test + + + io.quarkus + quarkus-test-keycloak-server + test + + + + + + + + org.openapitools + openapi-generator-maven-plugin + + jaxrs-spec + DTO + false + false + false + false + false + true + quarkus + + / + false + true + true + true + true + true + java8 + true + true + false + true + + + + + operator-product-v1 + + generate + + + src/main/openapi/onecx-product-store-operator-product-v1.yaml + gen.io.github.onecx.product.store.rs.operator.product.v1 + gen.io.github.onecx.product.store.rs.operator.product.v1.model + DTOV1 + + + + operator-mfe-v1 + + generate + + + src/main/openapi/onecx-product-store-operator-mfe-v1.yaml + gen.io.github.onecx.product.store.rs.operator.mfe.v1 + gen.io.github.onecx.product.store.rs.operator.mfe.v1.model + DTOV1 + + + + internal + + generate + + + src/main/openapi/onecx-product-store-internal.yaml + gen.io.github.onecx.product.store.rs.internal + gen.io.github.onecx.product.store.rs.internal.model + DTO + + + + external-v1 + + generate + + + src/main/openapi/onecx-product-store-v1.yaml + gen.io.github.onecx.product.store.rs.external.v1 + gen.io.github.onecx.product.store.rs.external.v1.model + DTOV1 + + + + + + + + + diff --git a/src/main/docker/Dockerfile.jvm b/src/main/docker/Dockerfile.jvm new file mode 100644 index 0000000..a554421 --- /dev/null +++ b/src/main/docker/Dockerfile.jvm @@ -0,0 +1,13 @@ +FROM registry.access.redhat.com/ubi9/openjdk-17:1.15 + +ENV LANGUAGE='en_US:en' + +COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/ +COPY --chown=185 target/quarkus-app/*.jar /deployments/ +COPY --chown=185 target/quarkus-app/app/ /deployments/app/ +COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/ + +EXPOSE 8080 +USER 185 +ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" \ No newline at end of file diff --git a/src/main/docker/Dockerfile.native b/src/main/docker/Dockerfile.native new file mode 100644 index 0000000..c97f217 --- /dev/null +++ b/src/main/docker/Dockerfile.native @@ -0,0 +1,11 @@ +FROM registry.access.redhat.com/ubi9/ubi-minimal:9.2 +WORKDIR /work/ +RUN chown 1001 /work \ + && chmod "g+rwX" /work \ + && chown 1001:root /work +COPY --chown=1001:root target/*-runner /work/application + +EXPOSE 8080 +USER 1001 + +CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] \ No newline at end of file diff --git a/src/main/helm/Chart.yaml b/src/main/helm/Chart.yaml new file mode 100644 index 0000000..f566aab --- /dev/null +++ b/src/main/helm/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +name: onecx-product-store-svc +version: 0.0.0 +appVersion: 0.0.0 +description: Onecx product store service +keywords: + - tenant +sources: + - https://github.com/onecx/onecx-product-store-svc +maintainers: + - name: Tkit Developer + email: tkit_dev@1000kit.org +dependencies: + - name: helm-quarkus-app + alias: app + version: ^0 + repository: oci://ghcr.io/onecx/charts \ No newline at end of file diff --git a/src/main/helm/values.yaml b/src/main/helm/values.yaml new file mode 100644 index 0000000..add1755 --- /dev/null +++ b/src/main/helm/values.yaml @@ -0,0 +1,6 @@ +app: + image: + repository: "onecx/onecx-theme-svc" + tag: 999-SNAPSHOT + db: + enabled: true \ No newline at end of file diff --git a/src/main/java/io/github/onecx/product/store/domain/CommonLogParam.java b/src/main/java/io/github/onecx/product/store/domain/CommonLogParam.java new file mode 100644 index 0000000..c15cda1 --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/domain/CommonLogParam.java @@ -0,0 +1,22 @@ +package io.github.onecx.product.store.domain; + +import java.util.List; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.core.Response; + +import org.jboss.resteasy.reactive.RestResponse; +import org.tkit.quarkus.log.cdi.LogParam; + +// FIXME: Move this to onecx-core library +@ApplicationScoped +public class CommonLogParam implements LogParam { + + @Override + public List getAssignableFrom() { + return List.of( + this.item(100, Response.class, x -> "Response:" + ((Response) x).getStatus()), + this.item(100, RestResponse.class, x -> "Response:" + ((RestResponse) x).getStatus()), + this.item(100, Exception.class, x -> x.getClass().getSimpleName() + ":" + ((Exception) x).getMessage())); + } +} diff --git a/src/main/java/io/github/onecx/product/store/domain/criteria/MicrofrontendSearchCriteria.java b/src/main/java/io/github/onecx/product/store/domain/criteria/MicrofrontendSearchCriteria.java new file mode 100644 index 0000000..bfc7c83 --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/domain/criteria/MicrofrontendSearchCriteria.java @@ -0,0 +1,19 @@ +package io.github.onecx.product.store.domain.criteria; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@RegisterForReflection +public class MicrofrontendSearchCriteria { + + private String displayName; + + private String productName; + + private Integer pageNumber; + + private Integer pageSize; +} diff --git a/src/main/java/io/github/onecx/product/store/domain/criteria/ProductSearchCriteria.java b/src/main/java/io/github/onecx/product/store/domain/criteria/ProductSearchCriteria.java new file mode 100644 index 0000000..84ec8c0 --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/domain/criteria/ProductSearchCriteria.java @@ -0,0 +1,17 @@ +package io.github.onecx.product.store.domain.criteria; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Getter; +import lombok.Setter; + +@RegisterForReflection +@Getter +@Setter +public class ProductSearchCriteria { + + private String name; + + private Integer pageNumber; + + private Integer pageSize; +} diff --git a/src/main/java/io/github/onecx/product/store/domain/daos/MicrofrontendDAO.java b/src/main/java/io/github/onecx/product/store/domain/daos/MicrofrontendDAO.java new file mode 100644 index 0000000..1cdb8e4 --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/domain/daos/MicrofrontendDAO.java @@ -0,0 +1,75 @@ +package io.github.onecx.product.store.domain.daos; + +import java.util.stream.Stream; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.persistence.NoResultException; +import jakarta.transaction.Transactional; + +import org.tkit.quarkus.jpa.daos.AbstractDAO; +import org.tkit.quarkus.jpa.daos.Page; +import org.tkit.quarkus.jpa.daos.PageResult; +import org.tkit.quarkus.jpa.exceptions.DAOException; +import org.tkit.quarkus.jpa.utils.QueryCriteriaUtil; + +import io.github.onecx.product.store.domain.criteria.MicrofrontendSearchCriteria; +import io.github.onecx.product.store.domain.models.Microfrontend; +import io.github.onecx.product.store.domain.models.Microfrontend_; + +@ApplicationScoped +@Transactional(Transactional.TxType.NOT_SUPPORTED) +public class MicrofrontendDAO extends AbstractDAO { + + public PageResult findMicrofrontendsByCriteria(MicrofrontendSearchCriteria criteria) { + try { + var cb = this.getEntityManager().getCriteriaBuilder(); + var cq = cb.createQuery(Microfrontend.class); + var root = cq.from(Microfrontend.class); + + if (criteria.getProductName() != null && !criteria.getProductName().isBlank()) { + cq.where(cb.like(root.get(Microfrontend_.PRODUCT_NAME), QueryCriteriaUtil.wildcard(criteria.getProductName()))); + } + + if (criteria.getDisplayName() != null && !criteria.getDisplayName().isBlank()) { + cq.where(cb.like(root.get(Microfrontend_.DISPLAY_NAME), QueryCriteriaUtil.wildcard(criteria.getDisplayName()))); + } + + return createPageQuery(cq, Page.of(criteria.getPageNumber(), criteria.getPageSize())).getPageResult(); + } catch (Exception ex) { + throw new DAOException(ErrorKeys.ERROR_FIND_MFE_BY_CRITERIA, ex); + } + } + + public Microfrontend findByMfeId(String mfeId) { + try { + var cb = this.getEntityManager().getCriteriaBuilder(); + var cq = cb.createQuery(Microfrontend.class); + var root = cq.from(Microfrontend.class); + cq.where(cb.equal(root.get(Microfrontend_.MFE_ID), mfeId)); + return this.getEntityManager().createQuery(cq).getSingleResult(); + } catch (NoResultException nre) { + return null; + } catch (Exception ex) { + throw new DAOException(ErrorKeys.ERROR_FIND_MFE_BY_ID, ex, mfeId); + } + } + + public Stream findByProductName(String productName) { + try { + var cb = this.getEntityManager().getCriteriaBuilder(); + var cq = cb.createQuery(Microfrontend.class); + var root = cq.from(Microfrontend.class); + cq.where(cb.equal(root.get(Microfrontend_.PRODUCT_NAME), productName)); + return this.getEntityManager().createQuery(cq).getResultStream(); + } catch (Exception ex) { + throw new DAOException(ErrorKeys.ERROR_FIND_MFES_BY_PRODUCT_NAME, ex, productName); + } + } + + public enum ErrorKeys { + + ERROR_FIND_MFE_BY_CRITERIA, + ERROR_FIND_MFES_BY_PRODUCT_NAME, + ERROR_FIND_MFE_BY_ID; + } +} diff --git a/src/main/java/io/github/onecx/product/store/domain/daos/ProductDAO.java b/src/main/java/io/github/onecx/product/store/domain/daos/ProductDAO.java new file mode 100644 index 0000000..ca0aaca --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/domain/daos/ProductDAO.java @@ -0,0 +1,57 @@ +package io.github.onecx.product.store.domain.daos; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.persistence.NoResultException; +import jakarta.transaction.Transactional; + +import org.tkit.quarkus.jpa.daos.AbstractDAO; +import org.tkit.quarkus.jpa.daos.Page; +import org.tkit.quarkus.jpa.daos.PageResult; +import org.tkit.quarkus.jpa.exceptions.DAOException; +import org.tkit.quarkus.jpa.utils.QueryCriteriaUtil; + +import io.github.onecx.product.store.domain.criteria.ProductSearchCriteria; +import io.github.onecx.product.store.domain.models.Product; +import io.github.onecx.product.store.domain.models.Product_; + +@ApplicationScoped +@Transactional(Transactional.TxType.NOT_SUPPORTED) +public class ProductDAO extends AbstractDAO { + + public PageResult findProductsByCriteria(ProductSearchCriteria criteria) { + try { + var cb = getEntityManager().getCriteriaBuilder(); + var cq = cb.createQuery(Product.class); + var root = cq.from(Product.class); + + if (criteria.getName() != null && !criteria.getName().isBlank()) { + cq.where(cb.like(root.get(Product_.NAME), QueryCriteriaUtil.wildcard(criteria.getName()))); + } + + return createPageQuery(cq, Page.of(criteria.getPageNumber(), criteria.getPageSize())).getPageResult(); + } catch (Exception ex) { + throw new DAOException(ErrorKeys.ERROR_FIND_PRODUCTS_BY_CRITERIA, ex); + } + } + + public Product findProductByName(String productName) { + try { + var cb = this.getEntityManager().getCriteriaBuilder(); + var cq = cb.createQuery(Product.class); + var root = cq.from(Product.class); + cq.where(cb.equal(root.get(Product_.name), productName)); + return this.getEntityManager().createQuery(cq).getSingleResult(); + } catch (NoResultException nre) { + return null; + } catch (Exception ex) { + throw new DAOException(ErrorKeys.ERROR_FIND_PRODUCT_BY_NAME, ex, productName); + } + } + + public enum ErrorKeys { + + ERROR_FIND_PRODUCTS_BY_CRITERIA, + + ERROR_FIND_PRODUCT_BY_NAME; + } +} diff --git a/src/main/java/io/github/onecx/product/store/domain/models/Microfrontend.java b/src/main/java/io/github/onecx/product/store/domain/models/Microfrontend.java new file mode 100644 index 0000000..5cc68c1 --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/domain/models/Microfrontend.java @@ -0,0 +1,79 @@ +package io.github.onecx.product.store.domain.models; + +import static jakarta.persistence.EnumType.STRING; + +import jakarta.persistence.*; + +import org.tkit.quarkus.jpa.models.TraceableEntity; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Entity +@Table(name = "PS_MICROFRONTEND", uniqueConstraints = { + @UniqueConstraint(name = "PS_MICROFRONTEND_UNIQUE", columnNames = { "REMOTE_ENTRY", "REMOTE_BASE_URL", + "EXPOSED_MODULE" }), + @UniqueConstraint(name = "PS_MICROFRONTEND_MFE_ID", columnNames = { "PRODUCT_NAME", "MFE_ID" }), + @UniqueConstraint(name = "PS_MICROFRONTEND_MFE_ID", columnNames = { "PRODUCT_NAME", "BASE_PATH" }) +}, indexes = { + @Index(name = "PS_MICROFRONTEND_PRODUCT_NAME", columnList = "PRODUCT_NAME") +}) +@SuppressWarnings("java:S2160") +public class Microfrontend extends TraceableEntity { + + @Column(name = "BASE_PATH") + private String basePath; + + @Column(name = "MFE_ID") + private String mfeId; + + @Column(name = "PRODUCT_NAME") + private String productName; + + @Column(name = "REMOTE_ENTRY") + private String remoteEntry; + + @Column(name = "REMOTE_NAME") + private String remoteName; + + @Column(name = "EXPOSED_MODULE") + private String exposedModule; + + @Column(name = "DISPLAY_NAME") + private String displayName; + + @Column(name = "WC_TAG_NAME") + private String wcTagName; + + @Column(name = "I18N_PATH") + private String i18nPath; + + @Column(name = "CONTACT", columnDefinition = "TEXT") + private String contact; + + @Column(name = "APP_ID") + private String appId; + + @Column(name = "APP_VERSION") + private String appVersion; + + @Column(name = "NOTE", columnDefinition = "TEXT") + private String note; + + @Column(name = "REMOTE_BASE_URL") + private String remoteBaseUrl; + + @Column(name = "MODULE_TYPE") + @Enumerated(STRING) + private ModuleType moduleType; + + @Column(name = "OPERATOR", nullable = false) + private boolean operator; + + public enum ModuleType { + ANGULAR, + WEBCOMPONENT + } +} diff --git a/src/main/java/io/github/onecx/product/store/domain/models/Product.java b/src/main/java/io/github/onecx/product/store/domain/models/Product.java new file mode 100644 index 0000000..10d11b7 --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/domain/models/Product.java @@ -0,0 +1,34 @@ +package io.github.onecx.product.store.domain.models; + +import jakarta.persistence.*; + +import org.tkit.quarkus.jpa.models.TraceableEntity; + +import lombok.Getter; +import lombok.Setter; + +@Entity +@Getter +@Setter +@Table(name = "PS_PRODUCT", uniqueConstraints = { + @UniqueConstraint(name = "UI_PS_PRODUCT_NAME", columnNames = { "NAME" }), + @UniqueConstraint(name = "UI_PS_PRODUCT_BASE_PATH", columnNames = { "BASE_PATH" }) +}) +@SuppressWarnings("squid:S2160") +public class Product extends TraceableEntity { + + @Column(name = "NAME") + private String name; + + @Column(name = "DESCRIPTION") + private String description; + + @Column(name = "IMAGE_URL") + private String imageUrl; + + @Column(name = "BASE_PATH") + private String basePath; + + @Column(name = "OPERATOR", nullable = false) + private boolean operator; +} diff --git a/src/main/java/io/github/onecx/product/store/rs/external/v1/controllers/ProductsRestControllerV1.java b/src/main/java/io/github/onecx/product/store/rs/external/v1/controllers/ProductsRestControllerV1.java new file mode 100644 index 0000000..948a333 --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/rs/external/v1/controllers/ProductsRestControllerV1.java @@ -0,0 +1,65 @@ +package io.github.onecx.product.store.rs.external.v1.controllers; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import jakarta.validation.ConstraintViolationException; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Response; + +import org.jboss.resteasy.reactive.RestResponse; +import org.jboss.resteasy.reactive.server.ServerExceptionMapper; +import org.tkit.quarkus.log.cdi.LogService; + +import gen.io.github.onecx.product.store.rs.external.v1.ProductsApi; +import gen.io.github.onecx.product.store.rs.external.v1.model.ProductSearchCriteriaDTOV1; +import gen.io.github.onecx.product.store.rs.external.v1.model.RestExceptionDTOV1; +import io.github.onecx.product.store.domain.daos.MicrofrontendDAO; +import io.github.onecx.product.store.domain.daos.ProductDAO; +import io.github.onecx.product.store.rs.external.v1.mappers.ExceptionMapperV1; +import io.github.onecx.product.store.rs.external.v1.mappers.ProductMapperV1; + +@Path("/v1/products") +@LogService +@ApplicationScoped +@Transactional(Transactional.TxType.NOT_SUPPORTED) +public class ProductsRestControllerV1 implements ProductsApi { + + @Inject + ExceptionMapperV1 exceptionMapper; + + @Inject + ProductMapperV1 mapper; + + @Inject + ProductDAO dao; + + @Inject + MicrofrontendDAO microfrontendDAO; + + @Override + public Response getProductByName(String name) { + var product = dao.findProductByName(name); + if (product == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + var microfrontends = microfrontendDAO.findByProductName(product.getName()); + + var dto = mapper.map(product, microfrontends); + + return Response.ok(dto).build(); + } + + @Override + public Response searchProducts(ProductSearchCriteriaDTOV1 productSearchCriteriaDTOV1) { + var criteria = mapper.map(productSearchCriteriaDTOV1); + var result = dao.findProductsByCriteria(criteria); + return Response.ok(mapper.mapPageResult(result)).build(); + } + + @ServerExceptionMapper + public RestResponse constraint(ConstraintViolationException ex) { + return exceptionMapper.constraint(ex); + } +} diff --git a/src/main/java/io/github/onecx/product/store/rs/external/v1/mappers/ExceptionMapperV1.java b/src/main/java/io/github/onecx/product/store/rs/external/v1/mappers/ExceptionMapperV1.java new file mode 100644 index 0000000..54e1988 --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/rs/external/v1/mappers/ExceptionMapperV1.java @@ -0,0 +1,56 @@ +package io.github.onecx.product.store.rs.external.v1.mappers; + +import java.util.List; +import java.util.Set; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.Path; +import jakarta.ws.rs.core.Response; + +import org.jboss.resteasy.reactive.RestResponse; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.tkit.quarkus.log.cdi.LogService; +import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper; + +import gen.io.github.onecx.product.store.rs.external.v1.model.*; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Mapper(uses = { OffsetDateTimeMapper.class }) +public abstract class ExceptionMapperV1 { + + @LogService(log = false) + public RestResponse constraint(ConstraintViolationException ex) { + var dto = exception("CONSTRAINT_VIOLATIONS", ex.getMessage()); + dto.setValidations(createErrorValidationResponse(ex.getConstraintViolations())); + return RestResponse.status(Response.Status.BAD_REQUEST, dto); + } + + @Mapping(target = "removeParametersItem", ignore = true) + @Mapping(target = "namedParameters", ignore = true) + @Mapping(target = "removeNamedParametersItem", ignore = true) + @Mapping(target = "parameters", ignore = true) + @Mapping(target = "validations", ignore = true) + @Mapping(target = "removeValidationsItem", ignore = true) + public abstract RestExceptionDTOV1 exception(String errorCode, String message); + + @Mapping(target = "removeParametersItem", ignore = true) + @Mapping(target = "namedParameters", ignore = true) + @Mapping(target = "removeNamedParametersItem", ignore = true) + @Mapping(target = "validations", ignore = true) + @Mapping(target = "removeValidationsItem", ignore = true) + public abstract RestExceptionDTOV1 exception(String errorCode, String message, List parameters); + + public abstract List createErrorValidationResponse( + Set> constraintViolation); + + @Mapping(target = "parameter", source = "propertyPath") + @Mapping(target = "message", source = "message") + public abstract ValidationConstraintDTOV1 createError(ConstraintViolation constraintViolation); + + public String mapPath(Path path) { + return path.toString(); + } +} diff --git a/src/main/java/io/github/onecx/product/store/rs/external/v1/mappers/ProductMapperV1.java b/src/main/java/io/github/onecx/product/store/rs/external/v1/mappers/ProductMapperV1.java new file mode 100644 index 0000000..a116296 --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/rs/external/v1/mappers/ProductMapperV1.java @@ -0,0 +1,39 @@ +package io.github.onecx.product.store.rs.external.v1.mappers; + +import java.util.List; +import java.util.stream.Stream; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.tkit.quarkus.jpa.daos.PageResult; +import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper; + +import gen.io.github.onecx.product.store.rs.external.v1.model.*; +import io.github.onecx.product.store.domain.criteria.ProductSearchCriteria; +import io.github.onecx.product.store.domain.models.Microfrontend; +import io.github.onecx.product.store.domain.models.Product; + +@Mapper(uses = { OffsetDateTimeMapper.class }) +public interface ProductMapperV1 { + + ProductSearchCriteria map(ProductSearchCriteriaDTOV1 data); + + @Mapping(target = "removeStreamItem", ignore = true) + ProductPageResultDTOV1 mapPageResult(PageResult page); + + ProductItemDTOV1 maPageItems(Product data); + + @Mapping(target = "removeMicrofrontendsItem", ignore = true) + @Mapping(target = "microfrontends", ignore = true) + ProductDTOV1 map(Product data); + + default ProductDTOV1 map(Product product, Stream microfrontends) { + var p = map(product); + p.setMicrofrontends(items(microfrontends)); + return p; + } + + List items(Stream microfrontends); + + MicrofrontendDTOV1 map(Microfrontend data); +} diff --git a/src/main/java/io/github/onecx/product/store/rs/internal/controllers/MicrofrontendsInternalRestController.java b/src/main/java/io/github/onecx/product/store/rs/internal/controllers/MicrofrontendsInternalRestController.java new file mode 100644 index 0000000..53b6bf9 --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/rs/internal/controllers/MicrofrontendsInternalRestController.java @@ -0,0 +1,98 @@ +package io.github.onecx.product.store.rs.internal.controllers; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import jakarta.validation.ConstraintViolationException; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriInfo; + +import org.jboss.resteasy.reactive.RestResponse; +import org.jboss.resteasy.reactive.server.ServerExceptionMapper; +import org.tkit.quarkus.jpa.exceptions.ConstraintException; +import org.tkit.quarkus.log.cdi.LogService; + +import gen.io.github.onecx.product.store.rs.internal.MicrofrontendsInternalApi; +import gen.io.github.onecx.product.store.rs.internal.model.CreateMicrofrontendDTO; +import gen.io.github.onecx.product.store.rs.internal.model.MicrofrontendSearchCriteriaDTO; +import gen.io.github.onecx.product.store.rs.internal.model.RestExceptionDTO; +import gen.io.github.onecx.product.store.rs.internal.model.UpdateMicrofrontendDTO; +import io.github.onecx.product.store.domain.daos.MicrofrontendDAO; +import io.github.onecx.product.store.domain.models.Microfrontend; +import io.github.onecx.product.store.rs.internal.mappers.InternalExceptionMapper; +import io.github.onecx.product.store.rs.internal.mappers.MicrofrontendMapper; + +@Path("/internal/microfrontends") +@LogService +@ApplicationScoped +@Transactional(Transactional.TxType.NOT_SUPPORTED) +public class MicrofrontendsInternalRestController implements MicrofrontendsInternalApi { + + @Inject + InternalExceptionMapper exceptionMapper; + + @Inject + MicrofrontendMapper mapper; + + @Inject + MicrofrontendDAO dao; + + @Context + UriInfo uriInfo; + + @Override + public Response createMicrofrontend(CreateMicrofrontendDTO createMicrofrontendDTO) { + var item = mapper.create(createMicrofrontendDTO); + item = dao.create(item); + return Response + .created(uriInfo.getAbsolutePathBuilder().path(item.getId()).build()) + .entity(mapper.map(item)) + .build(); + } + + @Override + public Response deleteMicrofrontend(String id) { + dao.deleteQueryById(id); + return Response.noContent().build(); + } + + @Override + public Response getMicrofrontend(String id) { + var item = dao.findById(id); + if (item == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + return Response.ok(mapper.map(item)).build(); + } + + @Override + public Response searchMicrofrontends(MicrofrontendSearchCriteriaDTO microfrontendSearchCriteriaDTO) { + var criteria = mapper.map(microfrontendSearchCriteriaDTO); + var result = dao.findMicrofrontendsByCriteria(criteria); + return Response.ok(mapper.mapPageResult(result)).build(); + } + + @Override + public Response updateMicrofrontend(String id, UpdateMicrofrontendDTO updateMicrofrontendDTO) { + Microfrontend item = dao.findById(id); + if (item == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + mapper.update(updateMicrofrontendDTO, item); + dao.update(item); + return Response.noContent().build(); + } + + @ServerExceptionMapper + public RestResponse exception(ConstraintException ex) { + return exceptionMapper.exception(ex); + } + + @ServerExceptionMapper + public RestResponse constraint(ConstraintViolationException ex) { + return exceptionMapper.constraint(ex); + } +} diff --git a/src/main/java/io/github/onecx/product/store/rs/internal/controllers/ProductsInternalRestController.java b/src/main/java/io/github/onecx/product/store/rs/internal/controllers/ProductsInternalRestController.java new file mode 100644 index 0000000..243fa9d --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/rs/internal/controllers/ProductsInternalRestController.java @@ -0,0 +1,95 @@ +package io.github.onecx.product.store.rs.internal.controllers; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import jakarta.validation.ConstraintViolationException; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriInfo; + +import org.jboss.resteasy.reactive.RestResponse; +import org.jboss.resteasy.reactive.server.ServerExceptionMapper; +import org.tkit.quarkus.jpa.exceptions.ConstraintException; +import org.tkit.quarkus.log.cdi.LogService; + +import gen.io.github.onecx.product.store.rs.internal.ProductsInternalApi; +import gen.io.github.onecx.product.store.rs.internal.model.*; +import io.github.onecx.product.store.domain.daos.ProductDAO; +import io.github.onecx.product.store.domain.models.Product; +import io.github.onecx.product.store.rs.internal.mappers.InternalExceptionMapper; +import io.github.onecx.product.store.rs.internal.mappers.ProductMapper; + +@Path("/internal/products") +@LogService +@ApplicationScoped +@Transactional(Transactional.TxType.NOT_SUPPORTED) +public class ProductsInternalRestController implements ProductsInternalApi { + + @Inject + InternalExceptionMapper exceptionMapper; + + @Inject + ProductMapper mapper; + + @Inject + ProductDAO dao; + + @Context + UriInfo uriInfo; + + @Override + public Response createProduct(CreateProductDTO createProductDTO) { + var item = mapper.create(createProductDTO); + item = dao.create(item); + return Response + .created(uriInfo.getAbsolutePathBuilder().path(item.getId()).build()) + .entity(mapper.map(item)) + .build(); + } + + @Override + public Response deleteProduct(String id) { + dao.deleteQueryById(id); + return Response.noContent().build(); + } + + @Override + public Response getProduct(String id) { + var item = dao.findById(id); + if (item == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + return Response.ok(mapper.map(item)).build(); + } + + @Override + public Response searchProducts(ProductSearchCriteriaDTO productSearchCriteriaDTO) { + var criteria = mapper.map(productSearchCriteriaDTO); + var result = dao.findProductsByCriteria(criteria); + return Response.ok(mapper.mapPageResult(result)).build(); + } + + @Override + public Response updateProduct(String id, UpdateProductDTO updateProductDTO) { + Product item = dao.findById(id); + if (item == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + mapper.update(updateProductDTO, item); + dao.update(item); + return Response.noContent().build(); + } + + @ServerExceptionMapper + public RestResponse exception(ConstraintException ex) { + return exceptionMapper.exception(ex); + } + + @ServerExceptionMapper + public RestResponse constraint(ConstraintViolationException ex) { + return exceptionMapper.constraint(ex); + } +} diff --git a/src/main/java/io/github/onecx/product/store/rs/internal/mappers/InternalExceptionMapper.java b/src/main/java/io/github/onecx/product/store/rs/internal/mappers/InternalExceptionMapper.java new file mode 100644 index 0000000..2c2ee77 --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/rs/internal/mappers/InternalExceptionMapper.java @@ -0,0 +1,64 @@ +package io.github.onecx.product.store.rs.internal.mappers; + +import java.util.List; +import java.util.Set; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.Path; +import jakarta.ws.rs.core.Response; + +import org.jboss.resteasy.reactive.RestResponse; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.tkit.quarkus.jpa.exceptions.ConstraintException; +import org.tkit.quarkus.log.cdi.LogService; +import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper; + +import gen.io.github.onecx.product.store.rs.internal.model.*; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Mapper(uses = { OffsetDateTimeMapper.class }) +public abstract class InternalExceptionMapper { + + @LogService(log = false) + public RestResponse constraint(ConstraintViolationException ex) { + var dto = exception("CONSTRAINT_VIOLATIONS", ex.getMessage()); + dto.setValidations(createErrorValidationResponse(ex.getConstraintViolations())); + return RestResponse.status(Response.Status.BAD_REQUEST, dto); + } + + @LogService(log = false) + public RestResponse exception(ConstraintException ce) { + var e = exception(ce.getMessageKey().name(), ce.getConstraints(), ce.parameters); + e.setNamedParameters(ce.namedParameters); + return RestResponse.status(Response.Status.BAD_REQUEST, e); + } + + @Mapping(target = "removeParametersItem", ignore = true) + @Mapping(target = "namedParameters", ignore = true) + @Mapping(target = "removeNamedParametersItem", ignore = true) + @Mapping(target = "parameters", ignore = true) + @Mapping(target = "validations", ignore = true) + @Mapping(target = "removeValidationsItem", ignore = true) + public abstract RestExceptionDTO exception(String errorCode, String message); + + @Mapping(target = "removeParametersItem", ignore = true) + @Mapping(target = "namedParameters", ignore = true) + @Mapping(target = "removeNamedParametersItem", ignore = true) + @Mapping(target = "validations", ignore = true) + @Mapping(target = "removeValidationsItem", ignore = true) + public abstract RestExceptionDTO exception(String errorCode, String message, List parameters); + + public abstract List createErrorValidationResponse( + Set> constraintViolation); + + @Mapping(target = "parameter", source = "propertyPath") + @Mapping(target = "message", source = "message") + public abstract ValidationConstraintDTO createError(ConstraintViolation constraintViolation); + + public String mapPath(Path path) { + return path.toString(); + } +} diff --git a/src/main/java/io/github/onecx/product/store/rs/internal/mappers/MicrofrontendMapper.java b/src/main/java/io/github/onecx/product/store/rs/internal/mappers/MicrofrontendMapper.java new file mode 100644 index 0000000..2cc6d32 --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/rs/internal/mappers/MicrofrontendMapper.java @@ -0,0 +1,45 @@ +package io.github.onecx.product.store.rs.internal.mappers; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.tkit.quarkus.jpa.daos.PageResult; +import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper; + +import gen.io.github.onecx.product.store.rs.internal.model.*; +import io.github.onecx.product.store.domain.criteria.MicrofrontendSearchCriteria; +import io.github.onecx.product.store.domain.models.Microfrontend; + +@Mapper(uses = { OffsetDateTimeMapper.class }) +public interface MicrofrontendMapper { + + @Mapping(target = "creationDate", ignore = true) + @Mapping(target = "creationUser", ignore = true) + @Mapping(target = "modificationDate", ignore = true) + @Mapping(target = "modificationUser", ignore = true) + @Mapping(target = "controlTraceabilityManual", ignore = true) + @Mapping(target = "modificationCount", ignore = true) + @Mapping(target = "persisted", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "operator", constant = "false") + Microfrontend create(CreateMicrofrontendDTO dto); + + @Mapping(target = "creationDate", ignore = true) + @Mapping(target = "creationUser", ignore = true) + @Mapping(target = "modificationDate", ignore = true) + @Mapping(target = "modificationUser", ignore = true) + @Mapping(target = "controlTraceabilityManual", ignore = true) + @Mapping(target = "modificationCount", ignore = true) + @Mapping(target = "persisted", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "operator", constant = "false") + void update(UpdateMicrofrontendDTO dto, @MappingTarget Microfrontend data); + + MicrofrontendSearchCriteria map(MicrofrontendSearchCriteriaDTO dto); + + @Mapping(target = "removeStreamItem", ignore = true) + MicrofrontendPageResultDTO mapPageResult(PageResult page); + + @Mapping(target = "version", source = "modificationCount") + MicrofrontendDTO map(Microfrontend data); +} diff --git a/src/main/java/io/github/onecx/product/store/rs/internal/mappers/ProductMapper.java b/src/main/java/io/github/onecx/product/store/rs/internal/mappers/ProductMapper.java new file mode 100644 index 0000000..4f465d7 --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/rs/internal/mappers/ProductMapper.java @@ -0,0 +1,45 @@ +package io.github.onecx.product.store.rs.internal.mappers; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.tkit.quarkus.jpa.daos.PageResult; +import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper; + +import gen.io.github.onecx.product.store.rs.internal.model.*; +import io.github.onecx.product.store.domain.criteria.ProductSearchCriteria; +import io.github.onecx.product.store.domain.models.Product; + +@Mapper(uses = { OffsetDateTimeMapper.class }) +public interface ProductMapper { + + ProductSearchCriteria map(ProductSearchCriteriaDTO data); + + @Mapping(target = "creationDate", ignore = true) + @Mapping(target = "creationUser", ignore = true) + @Mapping(target = "modificationDate", ignore = true) + @Mapping(target = "modificationUser", ignore = true) + @Mapping(target = "controlTraceabilityManual", ignore = true) + @Mapping(target = "modificationCount", ignore = true) + @Mapping(target = "persisted", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "operator", constant = "false") + Product create(CreateProductDTO dto); + + @Mapping(target = "creationDate", ignore = true) + @Mapping(target = "creationUser", ignore = true) + @Mapping(target = "modificationDate", ignore = true) + @Mapping(target = "modificationUser", ignore = true) + @Mapping(target = "controlTraceabilityManual", ignore = true) + @Mapping(target = "modificationCount", ignore = true) + @Mapping(target = "persisted", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "operator", constant = "false") + void update(UpdateProductDTO dto, @MappingTarget Product product); + + @Mapping(target = "removeStreamItem", ignore = true) + ProductPageResultDTO mapPageResult(PageResult page); + + @Mapping(target = "version", source = "modificationCount") + ProductDTO map(Product data); +} diff --git a/src/main/java/io/github/onecx/product/store/rs/operator/mfe/v1/controllers/OperatorMfeRestControllerV1.java b/src/main/java/io/github/onecx/product/store/rs/operator/mfe/v1/controllers/OperatorMfeRestControllerV1.java new file mode 100644 index 0000000..b10fb6c --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/rs/operator/mfe/v1/controllers/OperatorMfeRestControllerV1.java @@ -0,0 +1,61 @@ +package io.github.onecx.product.store.rs.operator.mfe.v1.controllers; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import jakarta.validation.ConstraintViolationException; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Response; + +import org.jboss.resteasy.reactive.RestResponse; +import org.jboss.resteasy.reactive.server.ServerExceptionMapper; +import org.tkit.quarkus.jpa.exceptions.ConstraintException; +import org.tkit.quarkus.log.cdi.LogService; + +import gen.io.github.onecx.product.store.rs.operator.mfe.v1.OperatorMfeApi; +import gen.io.github.onecx.product.store.rs.operator.mfe.v1.model.RestExceptionDTOV1; +import gen.io.github.onecx.product.store.rs.operator.mfe.v1.model.UpdateMfeRequestDTOV1; +import io.github.onecx.product.store.domain.daos.MicrofrontendDAO; +import io.github.onecx.product.store.rs.operator.mfe.v1.mappers.OperatorMfeExceptionMapperV1; +import io.github.onecx.product.store.rs.operator.mfe.v1.mappers.OperatorMfeMapperV1; + +@ApplicationScoped +@Transactional(Transactional.TxType.NOT_SUPPORTED) +@Path("/operator/mfe/v1/{mfeId}") +@LogService +public class OperatorMfeRestControllerV1 implements OperatorMfeApi { + + @Inject + MicrofrontendDAO dao; + + @Inject + OperatorMfeMapperV1 mapper; + + @Inject + OperatorMfeExceptionMapperV1 exceptionMapper; + + @Override + public Response createOrUpdateMfe(String mfeId, UpdateMfeRequestDTOV1 dto) { + + var mfe = dao.findByMfeId(mfeId); + if (mfe == null) { + mfe = mapper.create(dto); + mfe.setMfeId(mfeId); + dao.create(mfe); + return Response.status(Response.Status.CREATED).build(); + } + mapper.update(mfe, dto); + dao.update(mfe); + return Response.status(Response.Status.OK).build(); + } + + @ServerExceptionMapper + public RestResponse exception(ConstraintException ex) { + return exceptionMapper.exception(ex); + } + + @ServerExceptionMapper + public RestResponse constraint(ConstraintViolationException ex) { + return exceptionMapper.constraint(ex); + } +} diff --git a/src/main/java/io/github/onecx/product/store/rs/operator/mfe/v1/mappers/OperatorMfeExceptionMapperV1.java b/src/main/java/io/github/onecx/product/store/rs/operator/mfe/v1/mappers/OperatorMfeExceptionMapperV1.java new file mode 100644 index 0000000..3a30f35 --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/rs/operator/mfe/v1/mappers/OperatorMfeExceptionMapperV1.java @@ -0,0 +1,65 @@ +package io.github.onecx.product.store.rs.operator.mfe.v1.mappers; + +import java.util.List; +import java.util.Set; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.Path; +import jakarta.ws.rs.core.Response; + +import org.jboss.resteasy.reactive.RestResponse; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.tkit.quarkus.jpa.exceptions.ConstraintException; +import org.tkit.quarkus.log.cdi.LogService; +import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper; + +import gen.io.github.onecx.product.store.rs.operator.mfe.v1.model.RestExceptionDTOV1; +import gen.io.github.onecx.product.store.rs.operator.mfe.v1.model.ValidationConstraintDTOV1; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Mapper(uses = { OffsetDateTimeMapper.class }) +public abstract class OperatorMfeExceptionMapperV1 { + + @LogService(log = false) + public RestResponse constraint(ConstraintViolationException ex) { + var dto = exception("CONSTRAINT_VIOLATIONS", ex.getMessage()); + dto.setValidations(createErrorValidationResponse(ex.getConstraintViolations())); + return RestResponse.status(Response.Status.BAD_REQUEST, dto); + } + + @LogService(log = false) + public RestResponse exception(ConstraintException ce) { + var e = exception(ce.getMessageKey().name(), ce.getConstraints(), ce.parameters); + e.setNamedParameters(ce.namedParameters); + return RestResponse.status(Response.Status.BAD_REQUEST, e); + } + + @Mapping(target = "removeParametersItem", ignore = true) + @Mapping(target = "namedParameters", ignore = true) + @Mapping(target = "removeNamedParametersItem", ignore = true) + @Mapping(target = "parameters", ignore = true) + @Mapping(target = "validations", ignore = true) + @Mapping(target = "removeValidationsItem", ignore = true) + public abstract RestExceptionDTOV1 exception(String errorCode, String message); + + @Mapping(target = "removeParametersItem", ignore = true) + @Mapping(target = "namedParameters", ignore = true) + @Mapping(target = "removeNamedParametersItem", ignore = true) + @Mapping(target = "validations", ignore = true) + @Mapping(target = "removeValidationsItem", ignore = true) + public abstract RestExceptionDTOV1 exception(String errorCode, String message, List parameters); + + public abstract List createErrorValidationResponse( + Set> constraintViolation); + + @Mapping(target = "parameter", source = "propertyPath") + @Mapping(target = "message", source = "message") + public abstract ValidationConstraintDTOV1 createError(ConstraintViolation constraintViolation); + + public String mapPath(Path path) { + return path.toString(); + } +} diff --git a/src/main/java/io/github/onecx/product/store/rs/operator/mfe/v1/mappers/OperatorMfeMapperV1.java b/src/main/java/io/github/onecx/product/store/rs/operator/mfe/v1/mappers/OperatorMfeMapperV1.java new file mode 100644 index 0000000..bf6c2be --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/rs/operator/mfe/v1/mappers/OperatorMfeMapperV1.java @@ -0,0 +1,39 @@ +package io.github.onecx.product.store.rs.operator.mfe.v1.mappers; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper; + +import gen.io.github.onecx.product.store.rs.operator.mfe.v1.model.UpdateMfeRequestDTOV1; +import io.github.onecx.product.store.domain.models.Microfrontend; + +@Mapper(uses = { OffsetDateTimeMapper.class }) +public interface OperatorMfeMapperV1 { + + @Mapping(target = "creationDate", ignore = true) + @Mapping(target = "creationUser", ignore = true) + @Mapping(target = "modificationDate", ignore = true) + @Mapping(target = "modificationUser", ignore = true) + @Mapping(target = "controlTraceabilityManual", ignore = true) + @Mapping(target = "modificationCount", ignore = true) + @Mapping(target = "persisted", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "mfeId", ignore = true) + @Mapping(target = "i18nPath", ignore = true) + @Mapping(target = "operator", constant = "true") + Microfrontend create(UpdateMfeRequestDTOV1 dto); + + @Mapping(target = "creationDate", ignore = true) + @Mapping(target = "creationUser", ignore = true) + @Mapping(target = "modificationDate", ignore = true) + @Mapping(target = "modificationUser", ignore = true) + @Mapping(target = "controlTraceabilityManual", ignore = true) + @Mapping(target = "modificationCount", ignore = true) + @Mapping(target = "persisted", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "mfeId", ignore = true) + @Mapping(target = "i18nPath", ignore = true) + @Mapping(target = "operator", constant = "true") + void update(@MappingTarget Microfrontend mfe, UpdateMfeRequestDTOV1 dto); +} diff --git a/src/main/java/io/github/onecx/product/store/rs/operator/product/v1/controllers/OperatorProductRestControllerV1.java b/src/main/java/io/github/onecx/product/store/rs/operator/product/v1/controllers/OperatorProductRestControllerV1.java new file mode 100644 index 0000000..f038ded --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/rs/operator/product/v1/controllers/OperatorProductRestControllerV1.java @@ -0,0 +1,65 @@ +package io.github.onecx.product.store.rs.operator.product.v1.controllers; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import jakarta.validation.ConstraintViolationException; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Response; + +import org.jboss.resteasy.reactive.RestResponse; +import org.jboss.resteasy.reactive.server.ServerExceptionMapper; +import org.tkit.quarkus.jpa.exceptions.ConstraintException; +import org.tkit.quarkus.log.cdi.LogService; + +import gen.io.github.onecx.product.store.rs.operator.product.v1.OperatorProductApi; +import gen.io.github.onecx.product.store.rs.operator.product.v1.model.RestExceptionDTOV1; +import gen.io.github.onecx.product.store.rs.operator.product.v1.model.UpdateProductRequestDTOV1; +import io.github.onecx.product.store.domain.daos.ProductDAO; +import io.github.onecx.product.store.rs.operator.product.v1.mappers.OperatorProductExceptionMapperV1; +import io.github.onecx.product.store.rs.operator.product.v1.mappers.OperatorProductMapperV1; + +@ApplicationScoped +@Transactional(Transactional.TxType.NOT_SUPPORTED) +@Path("/operator/product/v1/update/{name}") +@LogService +public class OperatorProductRestControllerV1 implements OperatorProductApi { + + @Inject + ProductDAO dao; + + @Inject + OperatorProductMapperV1 mapper; + + @Inject + OperatorProductExceptionMapperV1 exceptionMapper; + + @Override + @Transactional(Transactional.TxType.REQUIRED) + public Response createOrUpdateProduct(String name, UpdateProductRequestDTOV1 dto) { + + var product = dao.findProductByName(name); + if (product == null) { + + product = mapper.create(dto); + product.setName(name); + dao.create(product); + + return Response.status(Response.Status.CREATED).build(); + } + + mapper.update(product, dto); + dao.update(product); + return Response.status(Response.Status.OK).build(); + } + + @ServerExceptionMapper + public Response exception(ConstraintException ex) { + return exceptionMapper.exception(ex); + } + + @ServerExceptionMapper + public RestResponse constraint(ConstraintViolationException ex) { + return exceptionMapper.constraint(ex); + } +} diff --git a/src/main/java/io/github/onecx/product/store/rs/operator/product/v1/mappers/OperatorProductExceptionMapperV1.java b/src/main/java/io/github/onecx/product/store/rs/operator/product/v1/mappers/OperatorProductExceptionMapperV1.java new file mode 100644 index 0000000..d031712 --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/rs/operator/product/v1/mappers/OperatorProductExceptionMapperV1.java @@ -0,0 +1,64 @@ +package io.github.onecx.product.store.rs.operator.product.v1.mappers; + +import java.util.List; +import java.util.Set; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.Path; +import jakarta.ws.rs.core.Response; + +import org.jboss.resteasy.reactive.RestResponse; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.tkit.quarkus.jpa.exceptions.ConstraintException; +import org.tkit.quarkus.log.cdi.LogService; +import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper; + +import gen.io.github.onecx.product.store.rs.operator.product.v1.model.*; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Mapper(uses = { OffsetDateTimeMapper.class }) +public abstract class OperatorProductExceptionMapperV1 { + + @LogService(log = false) + public RestResponse constraint(ConstraintViolationException ex) { + var dto = exception("CONSTRAINT_VIOLATIONS", ex.getMessage()); + dto.setValidations(createErrorValidationResponse(ex.getConstraintViolations())); + return RestResponse.status(Response.Status.BAD_REQUEST, dto); + } + + @LogService(log = false) + public Response exception(ConstraintException ce) { + var e = exception(ce.getMessageKey().name(), ce.getConstraints(), ce.parameters); + e.setNamedParameters(ce.namedParameters); + return Response.status(Response.Status.BAD_REQUEST).entity(e).build(); + } + + @Mapping(target = "removeParametersItem", ignore = true) + @Mapping(target = "namedParameters", ignore = true) + @Mapping(target = "removeNamedParametersItem", ignore = true) + @Mapping(target = "parameters", ignore = true) + @Mapping(target = "validations", ignore = true) + @Mapping(target = "removeValidationsItem", ignore = true) + public abstract RestExceptionDTOV1 exception(String errorCode, String message); + + @Mapping(target = "removeParametersItem", ignore = true) + @Mapping(target = "namedParameters", ignore = true) + @Mapping(target = "removeNamedParametersItem", ignore = true) + @Mapping(target = "validations", ignore = true) + @Mapping(target = "removeValidationsItem", ignore = true) + public abstract RestExceptionDTOV1 exception(String errorCode, String message, List parameters); + + public abstract List createErrorValidationResponse( + Set> constraintViolation); + + @Mapping(target = "parameter", source = "propertyPath") + @Mapping(target = "message", source = "message") + public abstract ValidationConstraintDTOV1 createError(ConstraintViolation constraintViolation); + + public String mapPath(Path path) { + return path.toString(); + } +} diff --git a/src/main/java/io/github/onecx/product/store/rs/operator/product/v1/mappers/OperatorProductMapperV1.java b/src/main/java/io/github/onecx/product/store/rs/operator/product/v1/mappers/OperatorProductMapperV1.java new file mode 100644 index 0000000..1ee2318 --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/rs/operator/product/v1/mappers/OperatorProductMapperV1.java @@ -0,0 +1,37 @@ +package io.github.onecx.product.store.rs.operator.product.v1.mappers; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper; + +import gen.io.github.onecx.product.store.rs.operator.product.v1.model.UpdateProductRequestDTOV1; +import io.github.onecx.product.store.domain.models.Product; + +@Mapper(uses = { OffsetDateTimeMapper.class }) +public interface OperatorProductMapperV1 { + + @Mapping(target = "creationDate", ignore = true) + @Mapping(target = "creationUser", ignore = true) + @Mapping(target = "modificationDate", ignore = true) + @Mapping(target = "modificationUser", ignore = true) + @Mapping(target = "controlTraceabilityManual", ignore = true) + @Mapping(target = "modificationCount", ignore = true) + @Mapping(target = "persisted", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "name", ignore = true) + @Mapping(target = "operator", constant = "true") + Product create(UpdateProductRequestDTOV1 dto); + + @Mapping(target = "creationDate", ignore = true) + @Mapping(target = "creationUser", ignore = true) + @Mapping(target = "modificationDate", ignore = true) + @Mapping(target = "modificationUser", ignore = true) + @Mapping(target = "controlTraceabilityManual", ignore = true) + @Mapping(target = "modificationCount", ignore = true) + @Mapping(target = "persisted", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "name", ignore = true) + @Mapping(target = "operator", constant = "true") + void update(@MappingTarget Product product, UpdateProductRequestDTOV1 dto); +} diff --git a/src/main/openapi/onecx-product-store-internal.yaml b/src/main/openapi/onecx-product-store-internal.yaml new file mode 100644 index 0000000..6a0f80b --- /dev/null +++ b/src/main/openapi/onecx-product-store-internal.yaml @@ -0,0 +1,607 @@ +--- +openapi: 3.0.3 +info: + title: onecx-product-store internal service + version: 1.0.0 +servers: + - url: "http://onecx-product-store:8080" +tags: + - name: microfrontendsInternal + - name: productsInternal +paths: + /internal/microfrontends: + post: + tags: + - microfrontendsInternal + description: Create micro-frontend + operationId: createMicrofrontend + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateMicrofrontend' + responses: + "201": + description: New micro-frontend created + headers: + Location: + required: true + schema: + type: string + format: url + content: + application/json: + schema: + $ref: '#/components/schemas/Microfrontend' + "400": + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/RestException' + /internal/microfrontends/search: + post: + tags: + - microfrontendsInternal + description: Search for micro-frontend by search criteria + operationId: searchMicrofrontends + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/MicrofrontendSearchCriteria' + responses: + "200": + description: Corresponding micro-frontends + content: + application/json: + schema: + $ref: '#/components/schemas/MicrofrontendPageResult' + "400": + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/RestException' + /internal/microfrontends/{id}: + get: + tags: + - microfrontendsInternal + description: Return micro-frontend by ID + operationId: getMicrofrontend + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Microfrontend' + "404": + description: Not found + put: + tags: + - microfrontendsInternal + description: Update microfrontend by ID + operationId: updateMicrofrontend + parameters: + - name: id + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateMicrofrontend' + responses: + "204": + description: Microfrontend updated + "400": + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/RestException' + "404": + description: Microfrontend not found + delete: + tags: + - microfrontendsInternal + description: Delete microfrontend by ID + operationId: deleteMicrofrontend + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + "204": + description: No Content + "400": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/RestException' + /internal/products: + post: + tags: + - productsInternal + description: Create new product + operationId: createProduct + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateProduct' + responses: + "201": + description: New product created + headers: + Location: + required: true + schema: + type: string + format: url + content: + application/json: + schema: + $ref: '#/components/schemas/Product' + "400": + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/RestException' + /internal/products/search: + post: + tags: + - productsInternal + description: Search for products by search criteria + operationId: searchProducts + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ProductSearchCriteria' + responses: + "200": + description: Corresponding products + content: + application/json: + schema: + $ref: '#/components/schemas/ProductPageResult' + "400": + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/RestException' + /internal/products/{id}: + get: + tags: + - productsInternal + description: Return product by ID + operationId: getProduct + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Product' + "400": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/RestException' + "404": + description: Not found + put: + tags: + - productsInternal + description: Update product by ID + operationId: updateProduct + parameters: + - name: id + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateProduct' + responses: + "204": + description: Product updated + "400": + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/RestException' + "404": + description: Product not found + delete: + tags: + - productsInternal + description: Delete product by ID + operationId: deleteProduct + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + "204": + description: No Content + "400": + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/RestException' +components: + schemas: + ProductPageResult: + type: object + properties: + totalElements: + format: int64 + description: The total elements in the resource. + type: integer + number: + format: int32 + type: integer + size: + format: int32 + type: integer + totalPages: + format: int64 + type: integer + stream: + type: array + items: + $ref: '#/components/schemas/Product' + ProductSearchCriteria: + type: object + properties: + name: + type: string + pageNumber: + format: int32 + description: The number of page. + default: 0 + type: integer + pageSize: + format: int32 + description: The size of page + default: 10 + type: integer + CreateProduct: + type: object + required: + - name + - basePath + properties: + name: + type: string + description: + type: string + imageUrl: + type: string + basePath: + type: string + UpdateProduct: + type: object + required: + - name + - basePath + properties: + name: + type: string + description: + type: string + imageUrl: + type: string + basePath: + type: string + Product: + type: object + required: + - id + - name + - operator + - basePath + properties: + id: + type: string + version: + format: int32 + type: integer + creationDate: + $ref: '#/components/schemas/OffsetDateTime' + creationUser: + type: string + modificationDate: + $ref: '#/components/schemas/OffsetDateTime' + modificationUser: + type: string + name: + type: string + description: + type: string + operator: + type: boolean + imageUrl: + type: string + basePath: + type: string + MicrofrontendPageResult: + type: object + properties: + totalElements: + format: int64 + description: The total elements in the resource. + type: integer + number: + format: int32 + type: integer + size: + format: int32 + type: integer + totalPages: + format: int64 + type: integer + stream: + type: array + items: + $ref: '#/components/schemas/Microfrontend' + MicrofrontendSearchCriteria: + type: object + properties: + displayName: + type: string + productName: + type: string + pageNumber: + format: int32 + description: The number of page. + default: 0 + type: integer + pageSize: + format: int32 + description: The size of page + default: 10 + type: integer + CreateMicrofrontend: + required: + - remoteEntry + - remoteName + - exposedModule + - displayName + - moduleType + - remoteBaseUrl + - productName + - basePath + - mfeId + type: object + properties: + mfeId: + minLength: 2 + type: string + basePath: + minLength: 2 + type: string + productName: + minLength: 2 + type: string + remoteEntry: + minLength: 2 + type: string + remoteName: + minLength: 2 + type: string + exposedModule: + minLength: 2 + type: string + displayName: + minLength: 2 + type: string + moduleType: + $ref: '#/components/schemas/ModuleType' + i18nPath: + minLength: 2 + type: string + wcTagName: + type: string + appId: + type: string + appVersion: + type: string + note: + type: string + contact: + type: string + remoteBaseUrl: + minLength: 2 + type: string + UpdateMicrofrontend: + required: + - remoteEntry + - remoteName + - exposedModule + - displayName + - moduleType + - remoteBaseUrl + - productName + - basePath + - mfeId + type: object + properties: + mfeId: + minLength: 2 + type: string + basePath: + minLength: 2 + type: string + productName: + minLength: 2 + type: string + remoteEntry: + minLength: 2 + type: string + remoteName: + minLength: 2 + type: string + exposedModule: + minLength: 2 + type: string + displayName: + minLength: 2 + type: string + moduleType: + $ref: '#/components/schemas/ModuleType' + i18nPath: + minLength: 2 + type: string + wcTagName: + type: string + appId: + type: string + appVersion: + type: string + note: + type: string + contact: + type: string + remoteBaseUrl: + minLength: 2 + type: string + Microfrontend: + required: + - remoteEntry + - remoteName + - exposedModule + - displayName + - moduleType + - remoteBaseUrl + - productName + - mfeId + - operator + - basePath + type: object + properties: + mfeId: + minLength: 2 + type: string + basePath: + minLength: 2 + type: string + operator: + type: boolean + productName: + minLength: 2 + type: string + version: + format: int32 + type: integer + creationDate: + $ref: '#/components/schemas/OffsetDateTime' + creationUser: + type: string + modificationDate: + $ref: '#/components/schemas/OffsetDateTime' + modificationUser: + type: string + id: + type: string + remoteEntry: + minLength: 2 + type: string + remoteName: + minLength: 2 + type: string + exposedModule: + minLength: 2 + type: string + displayName: + minLength: 2 + type: string + moduleType: + $ref: '#/components/schemas/ModuleType' + i18nPath: + minLength: 2 + type: string + wcTagName: + type: string + appId: + type: string + appVersion: + type: string + note: + type: string + contact: + type: string + remoteBaseUrl: + minLength: 2 + type: string + ModuleType: + enum: + - ANGULAR + - WEBCOMPONENT + type: string + OffsetDateTime: + format: date-time + type: string + example: 2022-03-10T12:15:50-04:00 + RestException: + type: object + properties: + errorCode: + type: string + message: + type: string + parameters: + type: array + items: + type: object + namedParameters: + type: object + additionalProperties: + type: object + validations: + type: array + items: + $ref: '#/components/schemas/ValidationConstraint' + ValidationConstraint: + type: object + properties: + parameter: + type: string + message: + type: string \ No newline at end of file diff --git a/src/main/openapi/onecx-product-store-operator-mfe-v1.yaml b/src/main/openapi/onecx-product-store-operator-mfe-v1.yaml new file mode 100644 index 0000000..ea0684e --- /dev/null +++ b/src/main/openapi/onecx-product-store-operator-mfe-v1.yaml @@ -0,0 +1,117 @@ +--- +openapi: 3.0.3 +info: + title: onecx-product-store mfe operator service + version: 1.0.0 +servers: + - url: "http://onecx-product-store:8080" +tags: + - name: operatorMfe +paths: + /operator/mfe/v1/{mfeId}: + put: + tags: + - operatorMfe + description: Creates or updates a product micro-frontend. + operationId: createOrUpdateMfe + parameters: + - name: mfeId + in: path + description: Mfe ID + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateMfeRequest' + responses: + "201": + description: Create new micro-frontend + "204": + description: Update existing micro-frontend + "400": + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/RestException' +components: + schemas: + RestException: + type: object + properties: + errorCode: + type: string + message: + type: string + parameters: + type: array + items: + type: object + namedParameters: + type: object + additionalProperties: + type: object + validations: + type: array + items: + $ref: '#/components/schemas/ValidationConstraint' + ValidationConstraint: + type: object + properties: + parameter: + type: string + message: + type: string + UpdateMfeRequest: + required: + - remoteEntry + - remoteName + - exposedModule + - displayName + - moduleType + - remoteBaseUrl + - productName + type: object + properties: + basePath: + minLength: 2 + type: string + productName: + minLength: 2 + type: string + remoteEntry: + minLength: 2 + type: string + remoteName: + minLength: 2 + type: string + exposedModule: + minLength: 2 + type: string + displayName: + minLength: 2 + type: string + moduleType: + $ref: '#/components/schemas/ModuleType' + wcTagName: + type: string + appId: + type: string + appVersion: + type: string + note: + type: string + contact: + type: string + remoteBaseUrl: + minLength: 2 + type: string + ModuleType: + enum: + - ANGULAR + - WEBCOMPONENT + type: string diff --git a/src/main/openapi/onecx-product-store-operator-product-v1.yaml b/src/main/openapi/onecx-product-store-operator-product-v1.yaml new file mode 100644 index 0000000..e4d8cb5 --- /dev/null +++ b/src/main/openapi/onecx-product-store-operator-product-v1.yaml @@ -0,0 +1,80 @@ +--- +openapi: 3.0.3 +info: + title: onecx-product-store product operator service + version: 1.0.0 +servers: + - url: "http://onecx-product-store:8080" +tags: + - name: operatorProduct +paths: + /operator/product/v1/update/{name}: + put: + tags: + - operatorProduct + description: Creates or updates a product registration. + operationId: createOrUpdateProduct + parameters: + - name: name + in: path + description: Product name + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateProductRequest' + responses: + "201": + description: Create new product + "204": + description: Update existing product + "400": + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/RestException' +components: + schemas: + RestException: + type: object + properties: + errorCode: + type: string + message: + type: string + parameters: + type: array + items: + type: object + namedParameters: + type: object + additionalProperties: + type: object + validations: + type: array + items: + $ref: '#/components/schemas/ValidationConstraint' + ValidationConstraint: + type: object + properties: + parameter: + type: string + message: + type: string + UpdateProductRequest: + required: + - basePath + type: object + properties: + basePath: + minLength: 2 + type: string + description: + type: string + image-url: + type: string diff --git a/src/main/openapi/onecx-product-store-v1.yaml b/src/main/openapi/onecx-product-store-v1.yaml new file mode 100644 index 0000000..fc874de --- /dev/null +++ b/src/main/openapi/onecx-product-store-v1.yaml @@ -0,0 +1,168 @@ +--- +openapi: 3.0.3 +info: + title: onecx-product-store external service + version: 1.0.0 +servers: + - url: "http://onecx-product-store:8080" +tags: + - name: products +paths: + /v1/products/{name}: + get: + tags: + - products + description: Get product by name + operationId: getProductByName + parameters: + - name: name + in: path + required: true + schema: + type: string + responses: + "200": + description: Corresponding product + content: + application/json: + schema: + $ref: '#/components/schemas/Product' + "404": + description: Product not found + /v1/products/search: + post: + tags: + - products + description: Search for products by search criteria + operationId: searchProducts + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ProductSearchCriteria' + responses: + "200": + description: Corresponding products + content: + application/json: + schema: + $ref: '#/components/schemas/ProductPageResult' + "400": + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/RestException' +components: + schemas: + ProductPageResult: + type: object + properties: + totalElements: + format: int64 + description: The total elements in the resource. + type: integer + number: + format: int32 + type: integer + size: + format: int32 + type: integer + totalPages: + format: int64 + type: integer + stream: + type: array + items: + $ref: '#/components/schemas/ProductItem' + ProductSearchCriteria: + type: object + properties: + name: + type: string + pageNumber: + format: int32 + description: The number of page. + default: 0 + type: integer + pageSize: + format: int32 + description: The size of page + default: 10 + type: integer + Product: + type: object + required: + - name + - basePath + properties: + name: + type: string + description: + type: string + imageUrl: + type: string + basePath: + type: string + microfrontends: + type: array + items: + $ref: '#/components/schemas/Microfrontend' + Microfrontend: + type: object + properties: + mfeId: + type: string + basePath: + type: string + displayName: + type: string + appId: + type: string + appVersion: + type: string + ProductItem: + type: object + required: + - name + - basePath + properties: + name: + type: string + description: + type: string + imageUrl: + type: string + basePath: + type: string + OffsetDateTime: + format: date-time + type: string + example: 2022-03-10T12:15:50-04:00 + RestException: + type: object + properties: + errorCode: + type: string + message: + type: string + parameters: + type: array + items: + type: object + namedParameters: + type: object + additionalProperties: + type: object + validations: + type: array + items: + $ref: '#/components/schemas/ValidationConstraint' + ValidationConstraint: + type: object + properties: + parameter: + type: string + message: + type: string \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..1f48018 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,22 @@ +# DEFAULT +quarkus.datasource.db-kind=postgresql +quarkus.datasource.jdbc.max-size=30 +quarkus.datasource.jdbc.min-size=10 + +quarkus.banner.enabled=false +quarkus.hibernate-orm.database.generation=validate +quarkus.liquibase.migrate-at-start=true +quarkus.liquibase.validate-on-migrate=true + +tkit.log.json.enabled=true + +# DEV +%dev.tkit.log.json.enabled=false + +# TEST +%test.tkit.log.json.enabled=false + +# PROD +%prod.quarkus.datasource.jdbc.url=${DB_URL:jdbc:postgresql://postgresdb:5432/onecx-product-store?sslmode=disable} +%prod.quarkus.datasource.username=${DB_USER:onecx-product-store} +%prod.quarkus.datasource.password=${DB_PWD:onecx-product-store} \ No newline at end of file diff --git a/src/main/resources/db/changeLog.xml b/src/main/resources/db/changeLog.xml new file mode 100644 index 0000000..edaa6a3 --- /dev/null +++ b/src/main/resources/db/changeLog.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/db/v1/2023-11-13-create-tables.xml b/src/main/resources/db/v1/2023-11-13-create-tables.xml new file mode 100644 index 0000000..1161fd3 --- /dev/null +++ b/src/main/resources/db/v1/2023-11-13-create-tables.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/io/github/onecx/product/store/AbstractTest.java b/src/test/java/io/github/onecx/product/store/AbstractTest.java new file mode 100644 index 0000000..ea0f182 --- /dev/null +++ b/src/test/java/io/github/onecx/product/store/AbstractTest.java @@ -0,0 +1,24 @@ +package io.github.onecx.product.store; + +import static com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS; +import static io.restassured.RestAssured.config; +import static io.restassured.config.ObjectMapperConfig.objectMapperConfig; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +import io.restassured.config.RestAssuredConfig; + +public abstract class AbstractTest { + + static { + config = RestAssuredConfig.config().objectMapperConfig( + objectMapperConfig().jackson2ObjectMapperFactory( + (cls, charset) -> { + var objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.configure(WRITE_DATES_AS_TIMESTAMPS, false); + return objectMapper; + })); + } +} diff --git a/src/test/java/io/github/onecx/product/store/domain/daos/MicrofrontendDAOTest.java b/src/test/java/io/github/onecx/product/store/domain/daos/MicrofrontendDAOTest.java new file mode 100644 index 0000000..04317d4 --- /dev/null +++ b/src/test/java/io/github/onecx/product/store/domain/daos/MicrofrontendDAOTest.java @@ -0,0 +1,43 @@ +package io.github.onecx.product.store.domain.daos; + +import jakarta.inject.Inject; +import jakarta.persistence.EntityManager; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.mockito.Mockito; +import org.tkit.quarkus.jpa.exceptions.DAOException; + +import io.quarkus.test.InjectMock; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class MicrofrontendDAOTest { + @Inject + MicrofrontendDAO dao; + + @InjectMock + EntityManager em; + + @BeforeEach + void beforeAll() { + Mockito.when(em.getCriteriaBuilder()).thenThrow(new RuntimeException("Test technical error exception")); + } + + @Test + void methodExceptionTests() { + methodExceptionTests(() -> dao.findByProductName(null), + MicrofrontendDAO.ErrorKeys.ERROR_FIND_MFES_BY_PRODUCT_NAME); + methodExceptionTests(() -> dao.findMicrofrontendsByCriteria(null), + MicrofrontendDAO.ErrorKeys.ERROR_FIND_MFE_BY_CRITERIA); + methodExceptionTests(() -> dao.findByMfeId("test"), + MicrofrontendDAO.ErrorKeys.ERROR_FIND_MFE_BY_ID); + } + + void methodExceptionTests(Executable fn, Enum key) { + var exc = Assertions.assertThrows(DAOException.class, fn); + Assertions.assertEquals(key, exc.key); + } +} diff --git a/src/test/java/io/github/onecx/product/store/domain/daos/ProductDAOTest.java b/src/test/java/io/github/onecx/product/store/domain/daos/ProductDAOTest.java new file mode 100644 index 0000000..cca999f --- /dev/null +++ b/src/test/java/io/github/onecx/product/store/domain/daos/ProductDAOTest.java @@ -0,0 +1,41 @@ +package io.github.onecx.product.store.domain.daos; + +import jakarta.inject.Inject; +import jakarta.persistence.EntityManager; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.mockito.Mockito; +import org.tkit.quarkus.jpa.exceptions.DAOException; + +import io.quarkus.test.InjectMock; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class ProductDAOTest { + @Inject + ProductDAO dao; + + @InjectMock + EntityManager em; + + @BeforeEach + void beforeAll() { + Mockito.when(em.getCriteriaBuilder()).thenThrow(new RuntimeException("Test technical error exception")); + } + + @Test + void methodExceptionTests() { + methodExceptionTests(() -> dao.findProductsByCriteria(null), + ProductDAO.ErrorKeys.ERROR_FIND_PRODUCTS_BY_CRITERIA); + methodExceptionTests(() -> dao.findProductByName("test"), + ProductDAO.ErrorKeys.ERROR_FIND_PRODUCT_BY_NAME); + } + + void methodExceptionTests(Executable fn, Enum key) { + var exc = Assertions.assertThrows(DAOException.class, fn); + Assertions.assertEquals(key, exc.key); + } +} diff --git a/src/test/java/io/github/onecx/product/store/rs/external/v1/controllers/ProductsRestControllerV1ExceptionTest.java b/src/test/java/io/github/onecx/product/store/rs/external/v1/controllers/ProductsRestControllerV1ExceptionTest.java new file mode 100644 index 0000000..514dfb3 --- /dev/null +++ b/src/test/java/io/github/onecx/product/store/rs/external/v1/controllers/ProductsRestControllerV1ExceptionTest.java @@ -0,0 +1,52 @@ +package io.github.onecx.product.store.rs.external.v1.controllers; + +import static io.restassured.RestAssured.given; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static jakarta.ws.rs.core.Response.Status.*; +import static org.mockito.ArgumentMatchers.any; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.tkit.quarkus.jpa.exceptions.DAOException; + +import gen.io.github.onecx.product.store.rs.external.v1.model.ProductSearchCriteriaDTOV1; +import io.github.onecx.product.store.AbstractTest; +import io.github.onecx.product.store.domain.daos.ProductDAO; +import io.quarkus.test.InjectMock; +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +@TestHTTPEndpoint(ProductsRestControllerV1.class) +class ProductsRestControllerV1ExceptionTest extends AbstractTest { + + @InjectMock + ProductDAO dao; + + @BeforeEach + void beforeAll() { + Mockito.when(dao.findProductsByCriteria(any())) + .thenThrow(new RuntimeException("Test technical error exception")) + .thenThrow(new DAOException(ProductDAO.ErrorKeys.ERROR_FIND_PRODUCT_BY_NAME, new RuntimeException("Test"))); + } + + @Test + void exceptionTest() { + + given() + .contentType(APPLICATION_JSON) + .body(new ProductSearchCriteriaDTOV1()) + .post("/search") + .then() + .statusCode(INTERNAL_SERVER_ERROR.getStatusCode()); + + var exception = given() + .contentType(APPLICATION_JSON) + .body(new ProductSearchCriteriaDTOV1()) + .post("/search") + .then() + .statusCode(INTERNAL_SERVER_ERROR.getStatusCode()); + } + +} diff --git a/src/test/java/io/github/onecx/product/store/rs/external/v1/controllers/ProductsRestControllerV1IT.java b/src/test/java/io/github/onecx/product/store/rs/external/v1/controllers/ProductsRestControllerV1IT.java new file mode 100644 index 0000000..388ba72 --- /dev/null +++ b/src/test/java/io/github/onecx/product/store/rs/external/v1/controllers/ProductsRestControllerV1IT.java @@ -0,0 +1,7 @@ +package io.github.onecx.product.store.rs.external.v1.controllers; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +class ProductsRestControllerV1IT extends ProductsRestControllerV1Test { +} diff --git a/src/test/java/io/github/onecx/product/store/rs/external/v1/controllers/ProductsRestControllerV1Test.java b/src/test/java/io/github/onecx/product/store/rs/external/v1/controllers/ProductsRestControllerV1Test.java new file mode 100644 index 0000000..3caaf57 --- /dev/null +++ b/src/test/java/io/github/onecx/product/store/rs/external/v1/controllers/ProductsRestControllerV1Test.java @@ -0,0 +1,83 @@ +package io.github.onecx.product.store.rs.external.v1.controllers; + +import static io.restassured.RestAssured.given; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static jakarta.ws.rs.core.Response.Status.*; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.tkit.quarkus.test.WithDBData; + +import gen.io.github.onecx.product.store.rs.external.v1.model.ProductDTOV1; +import gen.io.github.onecx.product.store.rs.external.v1.model.ProductPageResultDTOV1; +import gen.io.github.onecx.product.store.rs.external.v1.model.ProductSearchCriteriaDTOV1; +import gen.io.github.onecx.product.store.rs.external.v1.model.RestExceptionDTOV1; +import io.github.onecx.product.store.AbstractTest; +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +@TestHTTPEndpoint(ProductsRestControllerV1.class) +@WithDBData(value = "data/testdata-v1.xml", deleteBeforeInsert = true, deleteAfterTest = true, rinseAndRepeat = true) +class ProductsRestControllerV1Test extends AbstractTest { + + @Test + void getProductByNameTest() { + var dto = given() + .contentType(APPLICATION_JSON) + .pathParams("name", "product1") + .get("{name}") + .then().log().all() + .statusCode(OK.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract() + .as(ProductDTOV1.class); + + assertThat(dto).isNotNull(); + assertThat(dto.getName()).isEqualTo("product1"); + assertThat(dto.getMicrofrontends()).isNotNull().hasSize(2); + + given() + .contentType(APPLICATION_JSON) + .pathParams("name", "does-not-exists") + .get("{name}") + .then() + .statusCode(NOT_FOUND.getStatusCode()); + + } + + @Test + void searchProductsTest() { + + var criteria = new ProductSearchCriteriaDTOV1(); + + var data = given() + .contentType(APPLICATION_JSON) + .body(criteria) + .post("/search") + .then() + .statusCode(OK.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract() + .as(ProductPageResultDTOV1.class); + + assertThat(data).isNotNull(); + assertThat(data.getTotalElements()).isEqualTo(2); + assertThat(data.getStream()).isNotNull().hasSize(2); + } + + @Test + void searchProductsNoBodyTest() { + var data = given() + .contentType(APPLICATION_JSON) + .post("/search") + .then() + .statusCode(BAD_REQUEST.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract() + .as(RestExceptionDTOV1.class); + + assertThat(data).isNotNull(); + assertThat(data.getMessage()).isEqualTo("searchProducts.productSearchCriteriaDTOV1: must not be null"); + } +} diff --git a/src/test/java/io/github/onecx/product/store/rs/internal/controllers/MicrofrontendsInternalRestControllerIT.java b/src/test/java/io/github/onecx/product/store/rs/internal/controllers/MicrofrontendsInternalRestControllerIT.java new file mode 100644 index 0000000..1761813 --- /dev/null +++ b/src/test/java/io/github/onecx/product/store/rs/internal/controllers/MicrofrontendsInternalRestControllerIT.java @@ -0,0 +1,7 @@ +package io.github.onecx.product.store.rs.internal.controllers; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +class MicrofrontendsInternalRestControllerIT extends MicrofrontendsInternalRestControllerTest { +} diff --git a/src/test/java/io/github/onecx/product/store/rs/internal/controllers/MicrofrontendsInternalRestControllerTest.java b/src/test/java/io/github/onecx/product/store/rs/internal/controllers/MicrofrontendsInternalRestControllerTest.java new file mode 100644 index 0000000..cb7c642 --- /dev/null +++ b/src/test/java/io/github/onecx/product/store/rs/internal/controllers/MicrofrontendsInternalRestControllerTest.java @@ -0,0 +1,259 @@ +package io.github.onecx.product.store.rs.internal.controllers; + +import static io.restassured.RestAssured.given; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static jakarta.ws.rs.core.Response.Status.*; +import static jakarta.ws.rs.core.Response.Status.OK; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.tkit.quarkus.test.WithDBData; + +import gen.io.github.onecx.product.store.rs.internal.model.*; +import io.github.onecx.product.store.AbstractTest; +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +@TestHTTPEndpoint(MicrofrontendsInternalRestController.class) +@WithDBData(value = "data/testdata-internal.xml", deleteBeforeInsert = true, deleteAfterTest = true, rinseAndRepeat = true) +class MicrofrontendsInternalRestControllerTest extends AbstractTest { + + @Test + void createMicrofrontendTest() { + + CreateMicrofrontendDTO createDto = new CreateMicrofrontendDTO(); + createDto.setBasePath("basePath"); + createDto.setMfeId("mfeId"); + createDto.setExposedModule("exposed-module"); + createDto.setRemoteBaseUrl("remote-base-url"); + createDto.setRemoteEntry("remote-entry"); + createDto.setRemoteName("remote-name"); + createDto.setModuleType(ModuleTypeDTO.ANGULAR); + createDto.setDisplayName("display-name"); + createDto.setProductName("product-name"); + createDto.setRemoteBaseUrl("remote-base-url"); + + var dto = given() + .when() + .contentType(APPLICATION_JSON) + .body(createDto) + .post() + .then().log().all() + .statusCode(CREATED.getStatusCode()) + .extract() + .body().as(MicrofrontendDTO.class); + + assertThat(dto).isNotNull(); + assertThat(dto.getDisplayName()).isNotNull().isEqualTo(createDto.getDisplayName()); + assertThat(dto.getBasePath()).isNotNull().isEqualTo(createDto.getBasePath()); + + // create theme without body + var exception = given() + .when() + .contentType(APPLICATION_JSON) + .post() + .then() + .statusCode(BAD_REQUEST.getStatusCode()) + .extract().as(RestExceptionDTO.class); + + assertThat(exception.getErrorCode()).isEqualTo("CONSTRAINT_VIOLATIONS"); + assertThat(exception.getMessage()).isEqualTo("createMicrofrontend.createMicrofrontendDTO: must not be null"); + + // create theme with existing name + exception = given().when() + .contentType(APPLICATION_JSON) + .body(createDto) + .post() + .then() + .statusCode(BAD_REQUEST.getStatusCode()) + .extract().as(RestExceptionDTO.class); + + assertThat(exception.getErrorCode()).isEqualTo("PERSIST_ENTITY_FAILED"); + assertThat(exception.getMessage()).isEqualTo( + "could not execute statement [ERROR: duplicate key value violates unique constraint 'ps_microfrontend_unique' Detail: Key (remote_entry, remote_base_url, exposed_module)=(remote-entry, remote-base-url, exposed-module) already exists.]"); + + } + + @Test + void deleteMicrofrontendTest() { + // delete micro-frontend + given() + .contentType(APPLICATION_JSON) + .pathParam("id", "p1") + .delete("{id}") + .then().statusCode(NO_CONTENT.getStatusCode()); + + // check if micro-frontend exists + given() + .contentType(APPLICATION_JSON) + .pathParam("id", "p1") + .get("{id}") + .then().statusCode(NOT_FOUND.getStatusCode()); + + // delete micro-frontend + given() + .contentType(APPLICATION_JSON) + .pathParam("id", "p1") + .delete("{id}") + .then() + .statusCode(NO_CONTENT.getStatusCode()); + } + + @Test + void getMicrofrontendTest() { + + var dto = given() + .contentType(APPLICATION_JSON) + .pathParam("id", "m1") + .get("{id}") + .then().statusCode(OK.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract() + .body().as(MicrofrontendDTO.class); + + assertThat(dto).isNotNull(); + assertThat(dto.getProductName()).isEqualTo("product1"); + assertThat(dto.getDisplayName()).isEqualTo("display_name1"); + + given() + .contentType(APPLICATION_JSON) + .pathParam("id", "___") + .get("{id}") + .then().statusCode(NOT_FOUND.getStatusCode()); + } + + @Test + void searchMicrofrontendsTest() { + var criteria = new MicrofrontendSearchCriteriaDTO(); + + var data = given() + .contentType(APPLICATION_JSON) + .body(criteria) + .post("/search") + .then() + .statusCode(OK.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract() + .as(MicrofrontendPageResultDTO.class); + + assertThat(data).isNotNull(); + assertThat(data.getTotalElements()).isEqualTo(2); + assertThat(data.getStream()).isNotNull().hasSize(2); + + criteria.setProductName("product1"); + criteria.setDisplayName("display_name1"); + + data = given() + .contentType(APPLICATION_JSON) + .body(criteria) + .post("/search") + .then() + .statusCode(OK.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract() + .as(MicrofrontendPageResultDTO.class); + + assertThat(data).isNotNull(); + assertThat(data.getTotalElements()).isEqualTo(1); + assertThat(data.getStream()).isNotNull().hasSize(1); + + criteria.setProductName(" "); + criteria.setDisplayName(" "); + + data = given() + .contentType(APPLICATION_JSON) + .body(criteria) + .post("/search") + .then() + .statusCode(OK.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract() + .as(MicrofrontendPageResultDTO.class); + + assertThat(data).isNotNull(); + assertThat(data.getTotalElements()).isEqualTo(2); + assertThat(data.getStream()).isNotNull().hasSize(2); + } + + @Test + void searchProductsNoBodyTest() { + var data = given() + .contentType(APPLICATION_JSON) + .post("/search") + .then() + .statusCode(BAD_REQUEST.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract() + .as(RestExceptionDTO.class); + + assertThat(data).isNotNull(); + assertThat(data.getMessage()).isEqualTo("searchMicrofrontends.microfrontendSearchCriteriaDTO: must not be null"); + } + + @Test + void updateMicrofrontendTest() { + UpdateMicrofrontendDTO updateDto = new UpdateMicrofrontendDTO(); + updateDto.setBasePath("basePath"); + updateDto.setMfeId("mfeId"); + updateDto.setExposedModule("exposed-module"); + updateDto.setRemoteBaseUrl("remote-base-url"); + updateDto.setRemoteEntry("remote-entry"); + updateDto.setRemoteName("remote-name"); + updateDto.setModuleType(ModuleTypeDTO.ANGULAR); + updateDto.setDisplayName("display-name"); + updateDto.setProductName("product-name"); + updateDto.setRemoteBaseUrl("remote-base-url"); + + given() + .contentType(APPLICATION_JSON) + .body(updateDto) + .when() + .pathParam("id", "does-not-exists") + .put("{id}") + .then().log().all() + .statusCode(NOT_FOUND.getStatusCode()); + + given() + .contentType(APPLICATION_JSON) + .body(updateDto) + .when() + .pathParam("id", "m1") + .put("{id}") + .then().statusCode(NO_CONTENT.getStatusCode()); + + var dto = given().contentType(APPLICATION_JSON) + .body(updateDto) + .when() + .pathParam("id", "m1") + .get("{id}") + .then().statusCode(OK.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract() + .body().as(MicrofrontendDTO.class); + + assertThat(dto).isNotNull(); + assertThat(dto.getProductName()).isEqualTo(updateDto.getProductName()); + } + + @Test + void updateMicrofrontendWithoutBodyTest() { + + var exception = given() + .contentType(APPLICATION_JSON) + .when() + .pathParam("id", "update_create_new") + .put("{id}") + .then() + .statusCode(BAD_REQUEST.getStatusCode()) + .extract().as(RestExceptionDTO.class); + + Assertions.assertNotNull(exception); + Assertions.assertEquals("CONSTRAINT_VIOLATIONS", exception.getErrorCode()); + Assertions.assertEquals("updateMicrofrontend.updateMicrofrontendDTO: must not be null", + exception.getMessage()); + Assertions.assertNotNull(exception.getValidations()); + Assertions.assertEquals(1, exception.getValidations().size()); + } +} diff --git a/src/test/java/io/github/onecx/product/store/rs/internal/controllers/ProductsInternalRestControllerIT.java b/src/test/java/io/github/onecx/product/store/rs/internal/controllers/ProductsInternalRestControllerIT.java new file mode 100644 index 0000000..e822667 --- /dev/null +++ b/src/test/java/io/github/onecx/product/store/rs/internal/controllers/ProductsInternalRestControllerIT.java @@ -0,0 +1,7 @@ +package io.github.onecx.product.store.rs.internal.controllers; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +class ProductsInternalRestControllerIT extends ProductsInternalRestControllerTest { +} diff --git a/src/test/java/io/github/onecx/product/store/rs/internal/controllers/ProductsInternalRestControllerTest.java b/src/test/java/io/github/onecx/product/store/rs/internal/controllers/ProductsInternalRestControllerTest.java new file mode 100644 index 0000000..b10afc0 --- /dev/null +++ b/src/test/java/io/github/onecx/product/store/rs/internal/controllers/ProductsInternalRestControllerTest.java @@ -0,0 +1,240 @@ +package io.github.onecx.product.store.rs.internal.controllers; + +import static io.restassured.RestAssured.given; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static jakarta.ws.rs.core.Response.Status.*; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.tkit.quarkus.test.WithDBData; + +import gen.io.github.onecx.product.store.rs.internal.model.*; +import io.github.onecx.product.store.AbstractTest; +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +@TestHTTPEndpoint(ProductsInternalRestController.class) +@WithDBData(value = "data/testdata-internal.xml", deleteBeforeInsert = true, deleteAfterTest = true, rinseAndRepeat = true) +class ProductsInternalRestControllerTest extends AbstractTest { + + @Test + void createProductTest() { + + // create product + var createProductDTO = new CreateProductDTO(); + createProductDTO.setName("test01"); + createProductDTO.setBasePath("basePath"); + + var dto = given() + .when() + .contentType(APPLICATION_JSON) + .body(createProductDTO) + .post() + .then().log().all() + .statusCode(CREATED.getStatusCode()) + .extract() + .body().as(ProductDTO.class); + + assertThat(dto).isNotNull(); + assertThat(dto.getName()).isNotNull().isEqualTo(createProductDTO.getName()); + assertThat(dto.getBasePath()).isNotNull().isEqualTo(createProductDTO.getBasePath()); + + // create theme without body + var exception = given() + .when() + .contentType(APPLICATION_JSON) + .post() + .then() + .statusCode(BAD_REQUEST.getStatusCode()) + .extract().as(RestExceptionDTO.class); + + assertThat(exception.getErrorCode()).isEqualTo("CONSTRAINT_VIOLATIONS"); + assertThat(exception.getMessage()).isEqualTo("createProduct.createProductDTO: must not be null"); + + // create theme with existing name + exception = given().when() + .contentType(APPLICATION_JSON) + .body(createProductDTO) + .post() + .then() + .statusCode(BAD_REQUEST.getStatusCode()) + .extract().as(RestExceptionDTO.class); + + assertThat(exception.getErrorCode()).isEqualTo("PERSIST_ENTITY_FAILED"); + assertThat(exception.getMessage()).isEqualTo( + "could not execute statement [ERROR: duplicate key value violates unique constraint 'ui_ps_product_base_path' Detail: Key (base_path)=(basePath) already exists.]"); + } + + @Test + void deleteProductTest() { + // delete product + given() + .contentType(APPLICATION_JSON) + .pathParam("id", "p1") + .delete("{id}") + .then().statusCode(NO_CONTENT.getStatusCode()); + + // check if product exists + given() + .contentType(APPLICATION_JSON) + .pathParam("id", "p1") + .get("{id}") + .then().statusCode(NOT_FOUND.getStatusCode()); + + // delete product + given() + .contentType(APPLICATION_JSON) + .pathParam("id", "p1") + .delete("{id}") + .then() + .statusCode(NO_CONTENT.getStatusCode()); + } + + @Test + void getProductTest() { + var dto = given() + .contentType(APPLICATION_JSON) + .pathParam("id", "p1") + .get("{id}") + .then().statusCode(OK.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract() + .body().as(ProductDTO.class); + + assertThat(dto).isNotNull(); + assertThat(dto.getName()).isEqualTo("product1"); + assertThat(dto.getDescription()).isEqualTo("description"); + + given() + .contentType(APPLICATION_JSON) + .pathParam("id", "___") + .get("{id}") + .then().statusCode(NOT_FOUND.getStatusCode()); + } + + @Test + void searchProductsTest() { + var criteria = new ProductSearchCriteriaDTO(); + + var data = given() + .contentType(APPLICATION_JSON) + .body(criteria) + .post("/search") + .then() + .statusCode(OK.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract() + .as(ProductPageResultDTO.class); + + assertThat(data).isNotNull(); + assertThat(data.getTotalElements()).isEqualTo(2); + assertThat(data.getStream()).isNotNull().hasSize(2); + + criteria.setName("product1"); + + data = given() + .contentType(APPLICATION_JSON) + .body(criteria) + .post("/search") + .then() + .statusCode(OK.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract() + .as(ProductPageResultDTO.class); + + assertThat(data).isNotNull(); + assertThat(data.getTotalElements()).isEqualTo(1); + assertThat(data.getStream()).isNotNull().hasSize(1); + + criteria.setName(" "); + + data = given() + .contentType(APPLICATION_JSON) + .body(criteria) + .post("/search") + .then() + .statusCode(OK.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract() + .as(ProductPageResultDTO.class); + + assertThat(data).isNotNull(); + assertThat(data.getTotalElements()).isEqualTo(2); + assertThat(data.getStream()).isNotNull().hasSize(2); + } + + @Test + void searchProductsNoBodyTest() { + var data = given() + .contentType(APPLICATION_JSON) + .post("/search") + .then() + .statusCode(BAD_REQUEST.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract() + .as(RestExceptionDTO.class); + + assertThat(data).isNotNull(); + assertThat(data.getMessage()).isEqualTo("searchProducts.productSearchCriteriaDTO: must not be null"); + } + + @Test + void updateProductTest() { + var updateDto = new UpdateProductDTO(); + updateDto.setName("test01"); + updateDto.setDescription("description-update"); + updateDto.setBasePath("basePath"); + + given() + .contentType(APPLICATION_JSON) + .body(updateDto) + .when() + .pathParam("id", "does-not-exists") + .put("{id}") + .then().log().all() + .statusCode(NOT_FOUND.getStatusCode()); + + given() + .contentType(APPLICATION_JSON) + .body(updateDto) + .when() + .pathParam("id", "p1") + .put("{id}") + .then().statusCode(NO_CONTENT.getStatusCode()); + + var dto = given().contentType(APPLICATION_JSON) + .body(updateDto) + .when() + .pathParam("id", "p1") + .get("{id}") + .then().statusCode(OK.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract() + .body().as(ProductDTO.class); + + assertThat(dto).isNotNull(); + assertThat(dto.getDescription()).isEqualTo(updateDto.getDescription()); + } + + @Test + void updateProductWithoutBodyTest() { + + var exception = given() + .contentType(APPLICATION_JSON) + .when() + .pathParam("id", "update_create_new") + .put("{id}") + .then() + .statusCode(BAD_REQUEST.getStatusCode()) + .extract().as(RestExceptionDTO.class); + + Assertions.assertNotNull(exception); + Assertions.assertEquals("CONSTRAINT_VIOLATIONS", exception.getErrorCode()); + Assertions.assertEquals("updateProduct.updateProductDTO: must not be null", + exception.getMessage()); + Assertions.assertNotNull(exception.getValidations()); + Assertions.assertEquals(1, exception.getValidations().size()); + } +} diff --git a/src/test/java/io/github/onecx/product/store/rs/operator/mfe/v1/controllers/OperatorMfeRestControllerV1ExceptionTest.java b/src/test/java/io/github/onecx/product/store/rs/operator/mfe/v1/controllers/OperatorMfeRestControllerV1ExceptionTest.java new file mode 100644 index 0000000..3989143 --- /dev/null +++ b/src/test/java/io/github/onecx/product/store/rs/operator/mfe/v1/controllers/OperatorMfeRestControllerV1ExceptionTest.java @@ -0,0 +1,64 @@ +package io.github.onecx.product.store.rs.operator.mfe.v1.controllers; + +import static io.restassured.RestAssured.given; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static jakarta.ws.rs.core.Response.Status.*; +import static org.mockito.ArgumentMatchers.any; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.tkit.quarkus.jpa.exceptions.DAOException; + +import gen.io.github.onecx.product.store.rs.operator.mfe.v1.model.ModuleTypeDTOV1; +import gen.io.github.onecx.product.store.rs.operator.mfe.v1.model.UpdateMfeRequestDTOV1; +import io.github.onecx.product.store.AbstractTest; +import io.github.onecx.product.store.domain.daos.MicrofrontendDAO; +import io.quarkus.test.InjectMock; +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +@TestHTTPEndpoint(OperatorMfeRestControllerV1.class) +class OperatorMfeRestControllerV1ExceptionTest extends AbstractTest { + + @InjectMock + MicrofrontendDAO dao; + + @BeforeEach + void beforeAll() { + Mockito.when(dao.findByMfeId(any())) + .thenThrow(new RuntimeException("Test technical error exception")) + .thenThrow(new DAOException(MicrofrontendDAO.ErrorKeys.ERROR_FIND_MFE_BY_ID, new RuntimeException("Test"))); + } + + @Test + void exceptionTest() { + + var dto = new UpdateMfeRequestDTOV1(); + dto.setExposedModule("exposed-module"); + dto.setRemoteBaseUrl("remote-base-url"); + dto.setRemoteEntry("remote-entry"); + dto.setRemoteName("remote-name"); + dto.setModuleType(ModuleTypeDTOV1.ANGULAR); + dto.setDisplayName("display-name"); + dto.setProductName("product-name"); + dto.setRemoteBaseUrl("remote-base-url"); + + given() + .contentType(APPLICATION_JSON) + .body(dto) + .pathParam("mfeId", "mfe1") + .put() + .then().log().all() + .statusCode(INTERNAL_SERVER_ERROR.getStatusCode()); + + given() + .contentType(APPLICATION_JSON) + .body(dto) + .pathParam("mfeId", "mfe1") + .put() + .then() + .statusCode(INTERNAL_SERVER_ERROR.getStatusCode()); + } +} diff --git a/src/test/java/io/github/onecx/product/store/rs/operator/mfe/v1/controllers/OperatorMfeRestControllerV1IT.java b/src/test/java/io/github/onecx/product/store/rs/operator/mfe/v1/controllers/OperatorMfeRestControllerV1IT.java new file mode 100644 index 0000000..b833e34 --- /dev/null +++ b/src/test/java/io/github/onecx/product/store/rs/operator/mfe/v1/controllers/OperatorMfeRestControllerV1IT.java @@ -0,0 +1,8 @@ +package io.github.onecx.product.store.rs.operator.mfe.v1.controllers; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +class OperatorMfeRestControllerV1IT extends OperatorMfeRestControllerV1Test { + +} diff --git a/src/test/java/io/github/onecx/product/store/rs/operator/mfe/v1/controllers/OperatorMfeRestControllerV1Test.java b/src/test/java/io/github/onecx/product/store/rs/operator/mfe/v1/controllers/OperatorMfeRestControllerV1Test.java new file mode 100644 index 0000000..8073e39 --- /dev/null +++ b/src/test/java/io/github/onecx/product/store/rs/operator/mfe/v1/controllers/OperatorMfeRestControllerV1Test.java @@ -0,0 +1,113 @@ +package io.github.onecx.product.store.rs.operator.mfe.v1.controllers; + +import static io.restassured.RestAssured.given; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static jakarta.ws.rs.core.Response.Status.*; + +import org.junit.jupiter.api.Test; +import org.tkit.quarkus.test.WithDBData; + +import gen.io.github.onecx.product.store.rs.operator.mfe.v1.model.ModuleTypeDTOV1; +import gen.io.github.onecx.product.store.rs.operator.mfe.v1.model.UpdateMfeRequestDTOV1; +import gen.io.github.onecx.product.store.rs.operator.product.v1.model.UpdateProductRequestDTOV1; +import io.github.onecx.product.store.AbstractTest; +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +@TestHTTPEndpoint(OperatorMfeRestControllerV1.class) +@WithDBData(value = "data/testdata-operator-mfe.xml", deleteBeforeInsert = true, deleteAfterTest = true, rinseAndRepeat = true) +class OperatorMfeRestControllerV1Test extends AbstractTest { + + @Test + void createMfeTest() { + var dto = new UpdateMfeRequestDTOV1(); + dto.setExposedModule("exposed-module"); + dto.setRemoteBaseUrl("remote-base-url"); + dto.setRemoteEntry("remote-entry"); + dto.setRemoteName("remote-name"); + dto.setModuleType(ModuleTypeDTOV1.ANGULAR); + dto.setDisplayName("display-name"); + dto.setProductName("product-name"); + dto.setRemoteBaseUrl("remote-base-url"); + + given() + .when() + .contentType(APPLICATION_JSON) + .pathParam("mfeId", "new_mfe_id") + .body(dto) + .put() + .then() + .statusCode(CREATED.getStatusCode()); + } + + @Test + void createMfeUniqueErrorTest() { + var dto = new UpdateMfeRequestDTOV1(); + dto.setExposedModule("exposed-module1"); + dto.setRemoteBaseUrl("remote_base_url1"); + dto.setRemoteEntry("remote-entry1"); + dto.setRemoteName("remote-name"); + dto.setModuleType(ModuleTypeDTOV1.ANGULAR); + dto.setDisplayName("display-name"); + dto.setProductName("product-name"); + dto.setRemoteBaseUrl("remote_base_url1"); + + given() + .when() + .contentType(APPLICATION_JSON) + .pathParam("mfeId", "new_mfe_id") + .body(dto) + .put() + .then().log().all() + .statusCode(BAD_REQUEST.getStatusCode()); + } + + @Test + void updateMfeTest() { + var dto = new UpdateMfeRequestDTOV1(); + dto.setExposedModule("exposed-module"); + dto.setRemoteBaseUrl("remote-base-url"); + dto.setRemoteEntry("remote-entry"); + dto.setRemoteName("remote-name"); + dto.setModuleType(ModuleTypeDTOV1.ANGULAR); + dto.setDisplayName("display-name"); + dto.setProductName("product-name"); + dto.setRemoteBaseUrl("remote-base-url"); + + given() + .when() + .contentType(APPLICATION_JSON) + .pathParam("mfeId", "mfe1") + .body(dto) + .put() + .then() + .statusCode(OK.getStatusCode()); + } + + @Test + void createOrUpdateMfeNotValidTest() { + var dto = new UpdateProductRequestDTOV1(); + dto.setBasePath("/base_new"); + + given() + .when() + .contentType(APPLICATION_JSON) + .pathParam("mfeId", "new_mfe_id") + .body(dto) + .put() + .then() + .statusCode(BAD_REQUEST.getStatusCode()); + } + + @Test + void createOrUpdateMfeEmptyBodyTest() { + given() + .when() + .contentType(APPLICATION_JSON) + .pathParam("mfeId", "new_mfe_id") + .put() + .then().statusCode(BAD_REQUEST.getStatusCode()); + + } +} diff --git a/src/test/java/io/github/onecx/product/store/rs/operator/product/v1/controllers/OperatorProductRestControllerV1ExceptionTest.java b/src/test/java/io/github/onecx/product/store/rs/operator/product/v1/controllers/OperatorProductRestControllerV1ExceptionTest.java new file mode 100644 index 0000000..7b8f7ae --- /dev/null +++ b/src/test/java/io/github/onecx/product/store/rs/operator/product/v1/controllers/OperatorProductRestControllerV1ExceptionTest.java @@ -0,0 +1,57 @@ +package io.github.onecx.product.store.rs.operator.product.v1.controllers; + +import static io.restassured.RestAssured.given; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static jakarta.ws.rs.core.Response.Status.*; +import static org.mockito.ArgumentMatchers.any; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.tkit.quarkus.jpa.exceptions.DAOException; + +import gen.io.github.onecx.product.store.rs.operator.product.v1.model.UpdateProductRequestDTOV1; +import io.github.onecx.product.store.AbstractTest; +import io.github.onecx.product.store.domain.daos.ProductDAO; +import io.quarkus.test.InjectMock; +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +@TestHTTPEndpoint(OperatorProductRestControllerV1.class) +class OperatorProductRestControllerV1ExceptionTest extends AbstractTest { + + @InjectMock + ProductDAO dao; + + @BeforeEach + void beforeAll() { + Mockito.when(dao.findProductByName(any())) + .thenThrow(new RuntimeException("Test technical error exception")) + .thenThrow(new DAOException(ProductDAO.ErrorKeys.ERROR_FIND_PRODUCT_BY_NAME, new RuntimeException("Test"))); + } + + @Test + void exceptionTest() { + + var dto = new UpdateProductRequestDTOV1(); + dto.basePath("/new_product"); + + given() + .contentType(APPLICATION_JSON) + .body(dto) + .pathParam("name", "new_product_name") + .put() + .then().log().all() + .statusCode(INTERNAL_SERVER_ERROR.getStatusCode()); + + given() + .contentType(APPLICATION_JSON) + .body(dto) + .pathParam("name", "new_product_name") + .put() + .then() + .statusCode(INTERNAL_SERVER_ERROR.getStatusCode()); + } + +} diff --git a/src/test/java/io/github/onecx/product/store/rs/operator/product/v1/controllers/OperatorProductRestControllerV1IT.java b/src/test/java/io/github/onecx/product/store/rs/operator/product/v1/controllers/OperatorProductRestControllerV1IT.java new file mode 100644 index 0000000..2faf730 --- /dev/null +++ b/src/test/java/io/github/onecx/product/store/rs/operator/product/v1/controllers/OperatorProductRestControllerV1IT.java @@ -0,0 +1,8 @@ +package io.github.onecx.product.store.rs.operator.product.v1.controllers; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +class OperatorProductRestControllerV1IT extends OperatorProductRestControllerV1Test { + +} diff --git a/src/test/java/io/github/onecx/product/store/rs/operator/product/v1/controllers/OperatorProductRestControllerV1Test.java b/src/test/java/io/github/onecx/product/store/rs/operator/product/v1/controllers/OperatorProductRestControllerV1Test.java new file mode 100644 index 0000000..f8be328 --- /dev/null +++ b/src/test/java/io/github/onecx/product/store/rs/operator/product/v1/controllers/OperatorProductRestControllerV1Test.java @@ -0,0 +1,80 @@ +package io.github.onecx.product.store.rs.operator.product.v1.controllers; + +import static io.restassured.RestAssured.given; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static jakarta.ws.rs.core.Response.Status.*; + +import org.junit.jupiter.api.Test; +import org.tkit.quarkus.test.WithDBData; + +import gen.io.github.onecx.product.store.rs.operator.product.v1.model.UpdateProductRequestDTOV1; +import io.github.onecx.product.store.AbstractTest; +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +@TestHTTPEndpoint(OperatorProductRestControllerV1.class) +@WithDBData(value = "data/testdata-operator-product.xml", deleteBeforeInsert = true, deleteAfterTest = true, rinseAndRepeat = true) +class OperatorProductRestControllerV1Test extends AbstractTest { + + @Test + void createOrUpdateProductTest() { + + var dto = new UpdateProductRequestDTOV1(); + dto.basePath("/new_product"); + + given() + .when() + .contentType(APPLICATION_JSON) + .pathParam("name", "new_product_name") + .body(dto) + .put() + .then().statusCode(CREATED.getStatusCode()); + + } + + @Test + void createOrUpdateProductUpdateTest() { + + var dto = new UpdateProductRequestDTOV1(); + dto.basePath("/new_product"); + + given() + .when() + .contentType(APPLICATION_JSON) + .pathParam("name", "product1") + .body(dto) + .put() + .then().statusCode(OK.getStatusCode()); + + } + + @Test + void createOrUpdateProductExistingBaseUrlTest() { + + var dto = new UpdateProductRequestDTOV1(); + dto.basePath("/product1"); + + given() + .when() + .contentType(APPLICATION_JSON) + .pathParam("name", "new_product_name") + .body(dto) + .put() + .then().log().all() + .statusCode(BAD_REQUEST.getStatusCode()); + + } + + @Test + void createOrUpdateProductEmptyBodyTest() { + given() + .when() + .contentType(APPLICATION_JSON) + .pathParam("name", "new_product_name") + .put() + .then() + .statusCode(BAD_REQUEST.getStatusCode()); + + } +} diff --git a/src/test/resources/data/testdata-internal.xml b/src/test/resources/data/testdata-internal.xml new file mode 100644 index 0000000..812367c --- /dev/null +++ b/src/test/resources/data/testdata-internal.xml @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/data/testdata-operator-mfe.xml b/src/test/resources/data/testdata-operator-mfe.xml new file mode 100644 index 0000000..cbeb426 --- /dev/null +++ b/src/test/resources/data/testdata-operator-mfe.xml @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/src/test/resources/data/testdata-operator-product.xml b/src/test/resources/data/testdata-operator-product.xml new file mode 100644 index 0000000..52806e2 --- /dev/null +++ b/src/test/resources/data/testdata-operator-product.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/test/resources/data/testdata-v1.xml b/src/test/resources/data/testdata-v1.xml new file mode 100644 index 0000000..db421ef --- /dev/null +++ b/src/test/resources/data/testdata-v1.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file