From d5adf5d41fae827bcd2058454af1a39108dc3b26 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 26 Aug 2024 11:00:38 +0200 Subject: [PATCH 001/241] Add scaffold for new package --- packages/catalog-platformstate-writer/.env | 9 + .../catalog-platformstate-writer/Dockerfile | 44 ++++ packages/catalog-platformstate-writer/LICENSE | 201 ++++++++++++++++++ .../catalog-platformstate-writer/README.md | 11 + .../catalog-platformstate-writer/package.json | 46 ++++ .../src/config/config.ts | 15 ++ .../src/consumerServiceV1.ts | 28 +++ .../src/consumerServiceV2.ts | 36 ++++ .../catalog-platformstate-writer/src/index.ts | 40 ++++ .../tsconfig.check.json | 7 + .../tsconfig.json | 9 + .../vitest.config.ts | 11 + .../src/config/consumerServiceConfig.ts | 8 + .../tokenGenerationReadmodelDbConfig.ts | 23 ++ 14 files changed, 488 insertions(+) create mode 100644 packages/catalog-platformstate-writer/.env create mode 100644 packages/catalog-platformstate-writer/Dockerfile create mode 100644 packages/catalog-platformstate-writer/LICENSE create mode 100644 packages/catalog-platformstate-writer/README.md create mode 100644 packages/catalog-platformstate-writer/package.json create mode 100644 packages/catalog-platformstate-writer/src/config/config.ts create mode 100644 packages/catalog-platformstate-writer/src/consumerServiceV1.ts create mode 100644 packages/catalog-platformstate-writer/src/consumerServiceV2.ts create mode 100644 packages/catalog-platformstate-writer/src/index.ts create mode 100644 packages/catalog-platformstate-writer/tsconfig.check.json create mode 100644 packages/catalog-platformstate-writer/tsconfig.json create mode 100644 packages/catalog-platformstate-writer/vitest.config.ts create mode 100644 packages/commons/src/config/tokenGenerationReadmodelDbConfig.ts diff --git a/packages/catalog-platformstate-writer/.env b/packages/catalog-platformstate-writer/.env new file mode 100644 index 0000000000..05c1cc3684 --- /dev/null +++ b/packages/catalog-platformstate-writer/.env @@ -0,0 +1,9 @@ +LOG_LEVEL=info + +KAFKA_CLIENT_ID="catalog" +KAFKA_GROUP_ID="catalog-group" +KAFKA_BROKERS="localhost:9092" +KAFKA_DISABLE_AWS_IAM_AUTH="true" +CATALOG_TOPIC="event-store.catalog.events" +// TO DO add config for dynamoDB +AWS_REGION="eu-central-1" diff --git a/packages/catalog-platformstate-writer/Dockerfile b/packages/catalog-platformstate-writer/Dockerfile new file mode 100644 index 0000000000..071bcfce1d --- /dev/null +++ b/packages/catalog-platformstate-writer/Dockerfile @@ -0,0 +1,44 @@ +FROM node:20.14.0-slim@sha256:5e8ac65a0231d76a388683d07ca36a9769ab019a85d85169fe28e206f7a3208e as build + +RUN corepack enable + +WORKDIR /app +COPY package.json /app/ +COPY pnpm-lock.yaml /app/ +COPY pnpm-workspace.yaml /app/ + +COPY ./packages/catalog-platformstate-writer/package.json /app/packages/catalog-platformstate-writer/package.json +COPY ./packages/commons/package.json /app/packages/commons/package.json +COPY ./packages/models/package.json /app/packages/models/package.json +COPY ./packages/kafka-iam-auth/package.json /app/packages/kafka-iam-auth/package.json + +RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile + +COPY tsconfig.json /app/ +COPY turbo.json /app/ +COPY ./packages/catalog-platformstate-writer /app/packages/catalog-platformstate-writer +COPY ./packages/commons /app/packages/commons +COPY ./packages/models /app/packages/models +COPY ./packages/kafka-iam-auth /app/packages/kafka-iam-auth + +RUN pnpm build && \ + rm -rf /app/node_modules/.modules.yaml && \ + rm -rf /app/node_modules/.cache && \ + mkdir /out && \ + cp -a --parents -t /out \ + node_modules packages/catalog-platformstate-writer/node_modules \ + package*.json packages/catalog-platformstate-writer/package*.json \ + packages/commons \ + packages/models \ + packages/kafka-iam-auth \ + packages/catalog-platformstate-writer/dist && \ + find /out -exec touch -h --date=@0 {} \; + +FROM node:20.14.0-slim@sha256:5e8ac65a0231d76a388683d07ca36a9769ab019a85d85169fe28e206f7a3208e as final + +COPY --from=build /out /app + +WORKDIR /app/packages/catalog-platformstate-writer +EXPOSE 3000 + +CMD ["node", "."] diff --git a/packages/catalog-platformstate-writer/LICENSE b/packages/catalog-platformstate-writer/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/packages/catalog-platformstate-writer/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/catalog-platformstate-writer/README.md b/packages/catalog-platformstate-writer/README.md new file mode 100644 index 0000000000..f2c7b5dc44 --- /dev/null +++ b/packages/catalog-platformstate-writer/README.md @@ -0,0 +1,11 @@ +# pagopa-interop-be-event-consumer-poc + +Kafka consumer that consumes Debezium events and updates the read model + +Node version required >=node16 + +Run consumer + +``` +pnpm start +``` diff --git a/packages/catalog-platformstate-writer/package.json b/packages/catalog-platformstate-writer/package.json new file mode 100644 index 0000000000..86d977c4be --- /dev/null +++ b/packages/catalog-platformstate-writer/package.json @@ -0,0 +1,46 @@ +{ + "name": "pagopa-interop-catalog-platformstate-writer", + "private": true, + "version": "1.0.0", + "description": "PagoPA Interoperability catalog consumer service that updates the token-generation-read-model", + "main": "dist", + "type": "module", + "scripts": { + "test": "vitest", + "test:it": "vitest integration", + "lint": "eslint . --ext .ts,.tsx", + "lint:autofix": "eslint . --ext .ts,.tsx --fix", + "format:check": "prettier --check src", + "format:write": "prettier --write src", + "start": "node --loader ts-node/esm -r 'dotenv-flow/config' --watch ./src/index.ts", + "build": "tsc", + "check": "tsc --project tsconfig.check.json" + }, + "keywords": [], + "author": "", + "license": "Apache-2.0", + "devDependencies": { + "@pagopa/eslint-config": "3.0.0", + "@types/node": "20.14.6", + "@types/uuid": "9.0.8", + "date-fns": "3.6.0", + "pagopa-interop-commons-test": "workspace:*", + "prettier": "2.8.8", + "testcontainers": "10.9.0", + "ts-node": "10.9.2", + "typescript": "5.4.5", + "uuid": "10.0.0", + "vitest": "1.6.0" + }, + "dependencies": { + "@protobuf-ts/runtime": "2.9.4", + "connection-string": "4.4.0", + "dotenv-flow": "4.1.0", + "kafka-iam-auth": "workspace:*", + "kafkajs": "2.2.4", + "pagopa-interop-commons": "workspace:*", + "pagopa-interop-models": "workspace:*", + "ts-pattern": "5.2.0", + "zod": "3.23.8" + } +} diff --git a/packages/catalog-platformstate-writer/src/config/config.ts b/packages/catalog-platformstate-writer/src/config/config.ts new file mode 100644 index 0000000000..76cbaf6b8a --- /dev/null +++ b/packages/catalog-platformstate-writer/src/config/config.ts @@ -0,0 +1,15 @@ +import { + CatalogTopicConfig, + ReadModelWriterConfig, +} from "pagopa-interop-commons"; +import { z } from "zod"; + +export const CatalogReadModelWriterConfig = + ReadModelWriterConfig.and(CatalogTopicConfig); + +export type CatalogReadModelWriterConfig = z.infer< + typeof CatalogReadModelWriterConfig +>; + +export const config: CatalogReadModelWriterConfig = + CatalogReadModelWriterConfig.parse(process.env); diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts new file mode 100644 index 0000000000..0dde73f476 --- /dev/null +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -0,0 +1,28 @@ +import { EServiceCollection } from "pagopa-interop-commons"; +import { match } from "ts-pattern"; +import { EServiceEventEnvelopeV1 } from "pagopa-interop-models"; + +export async function handleMessageV1( + message: EServiceEventEnvelopeV1, + _eservices: EServiceCollection // TO DO dynamoDB table +): Promise { + await match(message) + .with( + { type: "EServiceAdded" }, + { type: "ClonedEServiceAdded" }, + { type: "EServiceUpdated" }, + { type: "EServiceRiskAnalysisAdded" }, + { type: "MovedAttributesFromEserviceToDescriptors" }, + { type: "EServiceRiskAnalysisUpdated" }, + { type: "EServiceWithDescriptorsDeleted" }, + { type: "EServiceDocumentUpdated" }, + { type: "EServiceDeleted" }, + { type: "EServiceDocumentAdded" }, + { type: "EServiceDocumentDeleted" }, + { type: "EServiceDescriptorAdded" }, + { type: "EServiceDescriptorUpdated" }, + { type: "EServiceRiskAnalysisDeleted" }, + () => Promise.resolve() + ) + .exhaustive(); +} diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts new file mode 100644 index 0000000000..9d8041f78a --- /dev/null +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -0,0 +1,36 @@ +import { EServiceCollection } from "pagopa-interop-commons"; +import { EServiceEventEnvelopeV2 } from "pagopa-interop-models"; +import { match } from "ts-pattern"; + +export async function handleMessageV2( + message: EServiceEventEnvelopeV2, + _eservices: EServiceCollection // TO DO dynamoDB table +): Promise { + await match(message) + .with( + { type: "EServiceDeleted" }, + { type: "EServiceAdded" }, + { type: "DraftEServiceUpdated" }, + { type: "EServiceCloned" }, + { type: "EServiceDescriptorAdded" }, + { type: "EServiceDraftDescriptorDeleted" }, + { type: "EServiceDraftDescriptorUpdated" }, + { type: "EServiceDescriptorQuotasUpdated" }, + { type: "EServiceDescriptorActivated" }, + { type: "EServiceDescriptorArchived" }, + { type: "EServiceDescriptorPublished" }, + { type: "EServiceDescriptorSuspended" }, + { type: "EServiceDescriptorInterfaceAdded" }, + { type: "EServiceDescriptorDocumentAdded" }, + { type: "EServiceDescriptorInterfaceUpdated" }, + { type: "EServiceDescriptorDocumentUpdated" }, + { type: "EServiceDescriptorInterfaceDeleted" }, + { type: "EServiceDescriptorDocumentDeleted" }, + { type: "EServiceRiskAnalysisAdded" }, + { type: "EServiceRiskAnalysisUpdated" }, + { type: "EServiceRiskAnalysisDeleted" }, + { type: "EServiceDescriptionUpdated" }, + () => Promise.resolve() + ) + .exhaustive(); +} diff --git a/packages/catalog-platformstate-writer/src/index.ts b/packages/catalog-platformstate-writer/src/index.ts new file mode 100644 index 0000000000..07aaccfca0 --- /dev/null +++ b/packages/catalog-platformstate-writer/src/index.ts @@ -0,0 +1,40 @@ +import { EachMessagePayload } from "kafkajs"; +import { + logger, + ReadModelRepository, + decodeKafkaMessage, +} from "pagopa-interop-commons"; +import { runConsumer } from "kafka-iam-auth"; +import { EServiceEvent } from "pagopa-interop-models"; +import { match } from "ts-pattern"; +import { handleMessageV1 } from "./consumerServiceV1.js"; +import { handleMessageV2 } from "./consumerServiceV2.js"; +import { config } from "./config/config.js"; + +const { eservices } = ReadModelRepository.init(config); + +async function processMessage({ + message, + partition, +}: EachMessagePayload): Promise { + const decodedMessage = decodeKafkaMessage(message, EServiceEvent); + + const loggerInstance = logger({ + serviceName: "catalog-platformstate-writer", + eventType: decodedMessage.type, + eventVersion: decodedMessage.event_version, + streamId: decodedMessage.stream_id, + correlationId: decodedMessage.correlation_id, + }); + + await match(decodedMessage) + .with({ event_version: 1 }, (msg) => handleMessageV1(msg, eservices)) + .with({ event_version: 2 }, (msg) => handleMessageV2(msg, eservices)) + .exhaustive(); + + loggerInstance.info( + `Token-generation read model was updated. Partition number: ${partition}. Offset: ${message.offset}` + ); +} + +await runConsumer(config, [config.catalogTopic], processMessage); diff --git a/packages/catalog-platformstate-writer/tsconfig.check.json b/packages/catalog-platformstate-writer/tsconfig.check.json new file mode 100644 index 0000000000..a19f84bcb7 --- /dev/null +++ b/packages/catalog-platformstate-writer/tsconfig.check.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "noEmit": true, + }, + "include": ["src", "test"] +} diff --git a/packages/catalog-platformstate-writer/tsconfig.json b/packages/catalog-platformstate-writer/tsconfig.json new file mode 100644 index 0000000000..a1ec44f6e6 --- /dev/null +++ b/packages/catalog-platformstate-writer/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src" + ] +} diff --git a/packages/catalog-platformstate-writer/vitest.config.ts b/packages/catalog-platformstate-writer/vitest.config.ts new file mode 100644 index 0000000000..9ece1be991 --- /dev/null +++ b/packages/catalog-platformstate-writer/vitest.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globalSetup: ["./test/vitestGlobalSetup.ts"], + testTimeout: 60000, + hookTimeout: 60000, + fileParallelism: false, + pool: "forks", + }, +}); diff --git a/packages/commons/src/config/consumerServiceConfig.ts b/packages/commons/src/config/consumerServiceConfig.ts index 25b126951a..aeacd46c82 100644 --- a/packages/commons/src/config/consumerServiceConfig.ts +++ b/packages/commons/src/config/consumerServiceConfig.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { KafkaConfig } from "./kafkaConfig.js"; import { ReadModelDbConfig } from "./readmodelDbConfig.js"; +import { TokenGenerationReadModelDbConfig } from "./tokenGenerationReadmodelDbConfig.js"; export const KafkaConsumerConfig = KafkaConfig.and( z @@ -19,3 +20,10 @@ export type KafkaConsumerConfig = z.infer; export const ReadModelWriterConfig = KafkaConsumerConfig.and(ReadModelDbConfig); export type ReadModelWriterConfig = z.infer; + +export const PlatformStateWriterConfig = KafkaConsumerConfig.and( + TokenGenerationReadModelDbConfig +); +export type PlatformStateWriterConfig = z.infer< + typeof PlatformStateWriterConfig +>; diff --git a/packages/commons/src/config/tokenGenerationReadmodelDbConfig.ts b/packages/commons/src/config/tokenGenerationReadmodelDbConfig.ts new file mode 100644 index 0000000000..64821e5fc6 --- /dev/null +++ b/packages/commons/src/config/tokenGenerationReadmodelDbConfig.ts @@ -0,0 +1,23 @@ +import { z } from "zod"; + +export const TokenGenerationReadModelDbConfig = z + .object({ + TOKEN_GENERATION_READMODEL_DB_HOST: z.string(), + TOKEN_GENERATION_READMODEL_DB_NAME: z.string(), + TOKEN_GENERATION_READMODEL_DB_USERNAME: z.string(), + TOKEN_GENERATION_READMODEL_DB_PASSWORD: z.string(), + TOKEN_GENERATION_READMODEL_DB_PORT: z.coerce.number().min(1001), + }) + .transform((c) => ({ + tokenGenerationReadModelDbHost: c.TOKEN_GENERATION_READMODEL_DB_HOST, + tokenGenerationReadModelDbName: c.TOKEN_GENERATION_READMODEL_DB_NAME, + tokenGenerationReadModelDbUsername: + c.TOKEN_GENERATION_READMODEL_DB_USERNAME, + tokenGenerationReadModelDbPassword: + c.TOKEN_GENERATION_READMODEL_DB_PASSWORD, + tokenGenerationReadModelDbPort: c.TOKEN_GENERATION_READMODEL_DB_PORT, + })); + +export type TokenGenerationReadModelDbConfig = z.infer< + typeof TokenGenerationReadModelDbConfig +>; From 91ba9ffc414a775accb4081a277d58ef9153d03d Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 26 Aug 2024 11:00:52 +0200 Subject: [PATCH 002/241] Add start script --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 5dde537710..2f205a93ec 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "scripts": { "start:catalog": "turbo start --filter pagopa-interop-catalog-process", "start:catalog-readmodel-writer": "turbo start --filter pagopa-interop-catalog-readmodel-writer", + "start:catalog-platformstate-writer": "turbo start --filter pagopa-interop-catalog-platformstate-writer", "start:agreement": "turbo start --filter pagopa-interop-agreement-process", "start:agreement-readmodel-writer": "turbo start --filter pagopa-interop-agreement-readmodel-writer", "start:agreement-email-sender": "turbo start --filter pagopa-interop-agreement-email-sender", From 002c8b1dd2ed1b389058c6ade8b5797ad346d882 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 26 Aug 2024 11:01:34 +0200 Subject: [PATCH 003/241] Refactor toV1 catalog converter --- .../test/catalogReadmodelWriter.integration.test.ts | 8 +++----- packages/commons-test/src/index.ts | 1 + .../catalogProtobufConverterToV1.ts} | 0 3 files changed, 4 insertions(+), 5 deletions(-) rename packages/{catalog-readmodel-writer/test/protobufConverterToV1.ts => commons-test/src/protobufConvertersToV1/catalogProtobufConverterToV1.ts} (100%) diff --git a/packages/catalog-readmodel-writer/test/catalogReadmodelWriter.integration.test.ts b/packages/catalog-readmodel-writer/test/catalogReadmodelWriter.integration.test.ts index 60974a4315..4df63491ba 100644 --- a/packages/catalog-readmodel-writer/test/catalogReadmodelWriter.integration.test.ts +++ b/packages/catalog-readmodel-writer/test/catalogReadmodelWriter.integration.test.ts @@ -2,6 +2,9 @@ import { describe, expect, it } from "vitest"; import { getMockValidRiskAnalysis, writeInReadmodel, + toEServiceV1, + toDocumentV1, + toDescriptorV1, } from "pagopa-interop-commons-test"; import { AttributeId, @@ -55,11 +58,6 @@ import { import { format } from "date-fns"; import { handleMessageV1 } from "../src/consumerServiceV1.js"; import { handleMessageV2 } from "../src/consumerServiceV2.js"; -import { - toEServiceV1, - toDocumentV1, - toDescriptorV1, -} from "./protobufConverterToV1.js"; import { eservices } from "./utils.js"; describe("database test", async () => { diff --git a/packages/commons-test/src/index.ts b/packages/commons-test/src/index.ts index 277203b8e6..449587b1f8 100644 --- a/packages/commons-test/src/index.ts +++ b/packages/commons-test/src/index.ts @@ -5,3 +5,4 @@ export * from "./containerTestUtils.js"; export * from "./riskAnalysisTestUtils.js"; export * from "./setupTestContainersVitest.js"; export * from "./setupTestContainersVitestGlobal.js"; +export * from "./protobufConvertersToV1/catalogProtobufConverterToV1.js"; diff --git a/packages/catalog-readmodel-writer/test/protobufConverterToV1.ts b/packages/commons-test/src/protobufConvertersToV1/catalogProtobufConverterToV1.ts similarity index 100% rename from packages/catalog-readmodel-writer/test/protobufConverterToV1.ts rename to packages/commons-test/src/protobufConvertersToV1/catalogProtobufConverterToV1.ts From 5ee8a64919154250204be7d3a92b5bd5e5e5e7fc Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 26 Aug 2024 11:01:55 +0200 Subject: [PATCH 004/241] Setup tests --- ...logPlatformstateWriter.integration.test.ts | 1394 +++++++++++++++++ .../test/tsconfig.json | 4 + .../test/utils.ts | 10 + .../test/vitestGlobalSetup.ts | 3 + 4 files changed, 1411 insertions(+) create mode 100644 packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts create mode 100644 packages/catalog-platformstate-writer/test/tsconfig.json create mode 100644 packages/catalog-platformstate-writer/test/utils.ts create mode 100644 packages/catalog-platformstate-writer/test/vitestGlobalSetup.ts diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts new file mode 100644 index 0000000000..5610501388 --- /dev/null +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -0,0 +1,1394 @@ +import { describe, expect, it } from "vitest"; +import { + getMockValidRiskAnalysis, + writeInReadmodel, + toEServiceV1, + toDocumentV1, + toDescriptorV1, +} from "pagopa-interop-commons-test"; +import { + AttributeId, + ClonedEServiceAddedV1, + Descriptor, + Document, + DraftEServiceUpdatedV2, + EService, + EServiceAddedV1, + EServiceAddedV2, + EServiceClonedV2, + EServiceDeletedV1, + EServiceDeletedV2, + EServiceDescriptorActivatedV2, + EServiceDescriptorAddedV1, + EServiceDescriptorAddedV2, + EServiceDescriptorArchivedV2, + EServiceDescriptorDocumentAddedV2, + EServiceDescriptorDocumentDeletedV2, + EServiceDescriptorDocumentUpdatedV2, + EServiceDescriptorInterfaceAddedV2, + EServiceDescriptorInterfaceDeletedV2, + EServiceDescriptorInterfaceUpdatedV2, + EServiceDescriptorPublishedV2, + EServiceDescriptorQuotasUpdatedV2, + EServiceDescriptorSuspendedV2, + EServiceDescriptorUpdatedV1, + EServiceDocumentAddedV1, + EServiceDocumentDeletedV1, + EServiceDocumentUpdatedV1, + EServiceDraftDescriptorDeletedV2, + EServiceDraftDescriptorUpdatedV2, + EServiceEventEnvelope, + EServiceReadModel, + EServiceRiskAnalysisAddedV1, + EServiceRiskAnalysisAddedV2, + EServiceRiskAnalysisDeletedV1, + EServiceRiskAnalysisDeletedV2, + EServiceUpdatedV1, + EServiceWithDescriptorsDeletedV1, + EserviceAttributes, + MovedAttributesFromEserviceToDescriptorsV1, + RiskAnalysis, + descriptorState, + eserviceMode, + generateId, + technology, + toEServiceV2, + toReadModelEService, +} from "pagopa-interop-models"; +import { format } from "date-fns"; +import { handleMessageV1 } from "../src/consumerServiceV1.js"; +import { handleMessageV2 } from "../src/consumerServiceV2.js"; +import { eservices } from "./utils.js"; + +describe("database test", async () => { + describe("Events V1", async () => { + const mockEService = getMockEService(); + + it("EServiceAdded", async () => { + const payload: EServiceAddedV1 = { + eservice: toEServiceV1(mockEService), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 1, + type: "EServiceAdded", + event_version: 1, + data: payload, + log_date: new Date(), + }; + await handleMessageV1(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("ClonedEServiceAdded", async () => { + await writeInReadmodel( + toReadModelEService(mockEService), + eservices, + 1 + ); + + const date = new Date(); + const clonedEService: EService = { + ...mockEService, + id: generateId(), + createdAt: new Date(), + name: `${mockEService.name} - clone - ${format( + date, + "dd/MM/yyyy HH:mm:ss" + )}`, + }; + + const payload: ClonedEServiceAddedV1 = { + eservice: toEServiceV1(clonedEService), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: clonedEService.id, + version: 1, + type: "ClonedEServiceAdded", + event_version: 1, + data: payload, + log_date: new Date(), + }; + await handleMessageV1(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceUpdated", async () => { + await writeInReadmodel( + toReadModelEService(mockEService), + eservices, + 1 + ); + + const updatedEService: EService = { + ...mockEService, + description: "updated description", + }; + const payload: EServiceUpdatedV1 = { + eservice: toEServiceV1(updatedEService), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceUpdated", + event_version: 1, + data: payload, + log_date: new Date(), + }; + await handleMessageV1(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceRiskAnalysisAdded", async () => { + await writeInReadmodel( + toReadModelEService(mockEService), + eservices, + 1 + ); + + const mockRiskAnalysis = getMockValidRiskAnalysis("PA"); + const updatedEService: EService = { + ...mockEService, + riskAnalysis: [...mockEService.riskAnalysis, mockRiskAnalysis], + }; + const payload: EServiceRiskAnalysisAddedV1 = { + eservice: toEServiceV1(updatedEService), + riskAnalysisId: mockRiskAnalysis.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceRiskAnalysisAdded", + event_version: 1, + data: payload, + log_date: new Date(), + }; + await handleMessageV1(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("MovedAttributesFromEserviceToDescriptors", async () => { + const attributes: EserviceAttributes = { + certified: [ + [ + { + id: generateId(), + explicitAttributeVerification: false, + }, + ], + ], + declared: [], + verified: [], + }; + const descriptor: Descriptor = { + ...getMockDescriptor(), + state: descriptorState.draft, + attributes, + }; + const eservice: EService = { + ...mockEService, + attributes, + descriptors: [descriptor], + }; + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + const updatedDescriptor = { + ...descriptor, + attributes, + }; + const updatedEService: EService = { + ...mockEService, + attributes: undefined, + descriptors: [updatedDescriptor], + }; + const payload: MovedAttributesFromEserviceToDescriptorsV1 = { + eservice: toEServiceV1(updatedEService), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "MovedAttributesFromEserviceToDescriptors", + event_version: 1, + data: payload, + log_date: new Date(), + }; + await handleMessageV1(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceWithDescriptorsDeleted", async () => { + const draftDescriptor: Descriptor = { + ...getMockDescriptor(), + state: descriptorState.draft, + }; + const eservice: EService = { + ...mockEService, + descriptors: [draftDescriptor], + }; + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const updatedEService: EService = { + ...eservice, + descriptors: [], + }; + const payload: EServiceWithDescriptorsDeletedV1 = { + eservice: toEServiceV1(updatedEService), + descriptorId: draftDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceWithDescriptorsDeleted", + event_version: 1, + data: payload, + log_date: new Date(), + }; + await handleMessageV1(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceDocumentUpdated", async () => { + const document = getMockDocument(); + const draftDescriptor: Descriptor = { + ...getMockDescriptor(), + state: descriptorState.draft, + docs: [document], + }; + const eservice: EService = { + ...mockEService, + descriptors: [draftDescriptor], + }; + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const updatedDocument: Document = { + ...document, + prettyName: "updated pretty name", + }; + + const payload: EServiceDocumentUpdatedV1 = { + eserviceId: eservice.id, + descriptorId: draftDescriptor.id, + documentId: document.id, + serverUrls: [], + updatedDocument: toDocumentV1(updatedDocument), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDocumentUpdated", + event_version: 1, + data: payload, + log_date: new Date(), + }; + await handleMessageV1(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceDeleted", async () => { + await writeInReadmodel(toReadModelEService(mockEService), eservices, 1); + + const payload: EServiceDeletedV1 = { + eserviceId: mockEService.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDeleted", + event_version: 1, + data: payload, + log_date: new Date(), + }; + await handleMessageV1(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + describe("EServiceDocumentAdded", () => { + it("interface", async () => { + const descriptorInterface = getMockDocument(); + const draftDescriptor: Descriptor = { + ...getMockDescriptor(), + state: descriptorState.draft, + docs: [], + }; + const eservice: EService = { + ...mockEService, + descriptors: [draftDescriptor], + }; + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const payload: EServiceDocumentAddedV1 = { + eserviceId: eservice.id, + descriptorId: draftDescriptor.id, + serverUrls: ["pagopa.it"], + document: toDocumentV1(descriptorInterface), + isInterface: true, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDocumentAdded", + event_version: 1, + data: payload, + log_date: new Date(), + }; + await handleMessageV1(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("document", async () => { + const document = getMockDocument(); + const draftDescriptor: Descriptor = { + ...getMockDescriptor(), + state: descriptorState.draft, + docs: [], + }; + const eservice: EService = { + ...mockEService, + descriptors: [draftDescriptor], + }; + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const payload: EServiceDocumentAddedV1 = { + eserviceId: eservice.id, + descriptorId: draftDescriptor.id, + serverUrls: [], + document: toDocumentV1(document), + isInterface: false, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDocumentAdded", + event_version: 1, + data: payload, + log_date: new Date(), + }; + await handleMessageV1(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + }); + + describe("EServiceDocumentDeleted", () => { + it("interface", async () => { + const descriptorInterface = getMockDocument(); + const draftDescriptor: Descriptor = { + ...getMockDescriptor(), + state: descriptorState.draft, + interface: descriptorInterface, + }; + const eservice: EService = { + ...mockEService, + descriptors: [draftDescriptor], + }; + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const payload: EServiceDocumentDeletedV1 = { + eserviceId: eservice.id, + descriptorId: draftDescriptor.id, + documentId: descriptorInterface.id, + }; + + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDocumentDeleted", + event_version: 1, + data: payload, + log_date: new Date(), + }; + await handleMessageV1(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("document", async () => { + const document = getMockDocument(); + const draftDescriptor: Descriptor = { + ...getMockDescriptor(), + state: descriptorState.draft, + docs: [document], + }; + const eservice: EService = { + ...mockEService, + descriptors: [draftDescriptor], + }; + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const payload: EServiceDocumentDeletedV1 = { + eserviceId: eservice.id, + descriptorId: draftDescriptor.id, + documentId: document.id, + }; + + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDocumentDeleted", + event_version: 1, + data: payload, + log_date: new Date(), + }; + await handleMessageV1(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + }); + + it("EServiceDescriptorAdded", async () => { + const draftDescriptor: Descriptor = { + ...getMockDescriptor(), + state: descriptorState.draft, + }; + const eservice: EService = { + ...mockEService, + descriptors: [], + }; + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const payload: EServiceDescriptorAddedV1 = { + eserviceId: eservice.id, + eserviceDescriptor: toDescriptorV1(draftDescriptor), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDescriptorAdded", + event_version: 1, + data: payload, + log_date: new Date(), + }; + await handleMessageV1(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceDescriptorUpdated", async () => { + const draftDescriptor: Descriptor = { + ...getMockDescriptor(), + interface: getMockDocument(), + state: descriptorState.draft, + }; + const eservice: EService = { + ...mockEService, + descriptors: [draftDescriptor], + }; + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const publishedDescriptor: Descriptor = { + ...draftDescriptor, + publishedAt: new Date(), + state: descriptorState.published, + }; + + const payload: EServiceDescriptorUpdatedV1 = { + eserviceId: eservice.id, + eserviceDescriptor: toDescriptorV1(publishedDescriptor), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDescriptorUpdated", + event_version: 1, + data: payload, + log_date: new Date(), + }; + await handleMessageV1(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceRiskAnalysisDeleted", async () => { + const riskAnalysis = getMockValidRiskAnalysis("PA"); + const eservice: EService = { + ...mockEService, + riskAnalysis: [riskAnalysis], + }; + + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const updatedEService: EService = { + ...mockEService, + riskAnalysis: [], + }; + const payload: EServiceRiskAnalysisDeletedV1 = { + eservice: toEServiceV1(updatedEService), + riskAnalysisId: riskAnalysis.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceRiskAnalysisDeleted", + event_version: 1, + data: payload, + log_date: new Date(), + }; + await handleMessageV1(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + }); + + describe("Events V2", async () => { + const mockEService = getMockEService(); + it("EServiceDeleted", async () => { + await writeInReadmodel(toReadModelEService(mockEService), eservices, 1); + + const payload: EServiceDeletedV2 = { + eserviceId: mockEService.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDeleted", + event_version: 2, + data: payload, + log_date: new Date(), + }; + await handleMessageV2(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceAdded", async () => { + const payload: EServiceAddedV2 = { + eservice: toEServiceV2(mockEService), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 1, + type: "EServiceAdded", + event_version: 2, + data: payload, + log_date: new Date(), + }; + await handleMessageV2(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("DraftEServiceUpdated", async () => { + await writeInReadmodel( + toReadModelEService(mockEService), + eservices, + 1 + ); + + const updatedEService: EService = { + ...mockEService, + description: "updated description", + }; + const payload: DraftEServiceUpdatedV2 = { + eservice: toEServiceV2(updatedEService), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "DraftEServiceUpdated", + event_version: 2, + data: payload, + log_date: new Date(), + }; + await handleMessageV2(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceCloned", async () => { + const sourceDescriptor: Descriptor = { + ...getMockDescriptor(), + state: descriptorState.published, + publishedAt: new Date(), + interface: getMockDocument(), + }; + const sourceEService: EService = { + ...mockEService, + descriptors: [sourceDescriptor], + }; + await writeInReadmodel( + toReadModelEService(sourceEService), + eservices, + 1 + ); + + const date = new Date(); + const clonedEService: EService = { + ...sourceEService, + id: generateId(), + createdAt: new Date(), + name: `${mockEService.name} - clone - ${format( + date, + "dd/MM/yyyy HH:mm:ss" + )}`, + descriptors: [ + { + ...sourceDescriptor, + publishedAt: undefined, + state: descriptorState.draft, + }, + ], + }; + + const payload: EServiceClonedV2 = { + sourceEservice: toEServiceV2(sourceEService), + sourceDescriptorId: sourceDescriptor.id, + eservice: toEServiceV2(clonedEService), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: clonedEService.id, + version: 1, + type: "EServiceCloned", + event_version: 2, + data: payload, + log_date: new Date(), + }; + await handleMessageV2(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceDescriptorAdded", async () => { + const draftDescriptor: Descriptor = { + ...getMockDescriptor(), + state: descriptorState.draft, + }; + const eservice: EService = { + ...mockEService, + descriptors: [], + }; + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const updatedEService: EService = { + ...eservice, + descriptors: [draftDescriptor], + }; + const payload: EServiceDescriptorAddedV2 = { + eservice: toEServiceV2(updatedEService), + descriptorId: draftDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDescriptorAdded", + event_version: 2, + data: payload, + log_date: new Date(), + }; + await handleMessageV2(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceDraftDescriptorDeleted", async () => { + const draftDescriptor: Descriptor = { + ...getMockDescriptor(), + state: descriptorState.draft, + }; + const eservice: EService = { + ...mockEService, + descriptors: [draftDescriptor], + }; + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const updatedEService: EService = { + ...eservice, + descriptors: [], + }; + const payload: EServiceDraftDescriptorDeletedV2 = { + eservice: toEServiceV2(updatedEService), + descriptorId: draftDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDraftDescriptorDeleted", + event_version: 2, + data: payload, + log_date: new Date(), + }; + await handleMessageV2(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceDraftDescriptorUpdated", async () => { + const draftDescriptor: Descriptor = { + ...getMockDescriptor(), + interface: getMockDocument(), + state: descriptorState.draft, + }; + const eservice: EService = { + ...mockEService, + descriptors: [draftDescriptor], + }; + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const updatedDraftDescriptor: Descriptor = { + ...draftDescriptor, + description: "updated description", + }; + const updatedEService: EService = { + ...eservice, + descriptors: [updatedDraftDescriptor], + }; + const payload: EServiceDraftDescriptorUpdatedV2 = { + eservice: toEServiceV2(updatedEService), + descriptorId: updatedDraftDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDraftDescriptorUpdated", + event_version: 2, + data: payload, + log_date: new Date(), + }; + await handleMessageV2(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceDescriptorQuotasUpdated", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + interface: getMockDocument(), + state: descriptorState.published, + publishedAt: new Date(), + }; + const eservice: EService = { + ...mockEService, + descriptors: [publishedDescriptor], + }; + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const updatedPublishedDescriptor: Descriptor = { + ...publishedDescriptor, + dailyCallsTotal: publishedDescriptor.dailyCallsTotal + 1000, + }; + const updatedEService: EService = { + ...eservice, + descriptors: [updatedPublishedDescriptor], + }; + const payload: EServiceDescriptorQuotasUpdatedV2 = { + eservice: toEServiceV2(updatedEService), + descriptorId: updatedPublishedDescriptor.id, + }; + + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDescriptorQuotasUpdated", + event_version: 2, + data: payload, + log_date: new Date(), + }; + await handleMessageV2(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceDescriptorActivated", async () => { + const suspendedDescriptor: Descriptor = { + ...getMockDescriptor(), + interface: getMockDocument(), + state: descriptorState.suspended, + publishedAt: new Date(), + suspendedAt: new Date(), + }; + const eservice: EService = { + ...mockEService, + descriptors: [suspendedDescriptor], + }; + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const publishedDescriptor: Descriptor = { + ...suspendedDescriptor, + publishedAt: new Date(), + suspendedAt: new Date(), + state: descriptorState.published, + }; + const updatedEService: EService = { + ...eservice, + descriptors: [publishedDescriptor], + }; + const payload: EServiceDescriptorActivatedV2 = { + eservice: toEServiceV2(updatedEService), + descriptorId: publishedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDescriptorActivated", + event_version: 2, + data: payload, + log_date: new Date(), + }; + await handleMessageV2(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceDescriptorArchived", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + interface: getMockDocument(), + state: descriptorState.published, + publishedAt: new Date(), + }; + const eservice: EService = { + ...mockEService, + descriptors: [publishedDescriptor], + }; + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const archivedDescriptor: Descriptor = { + ...publishedDescriptor, + archivedAt: new Date(), + state: descriptorState.archived, + }; + const updatedEService: EService = { + ...eservice, + descriptors: [archivedDescriptor], + }; + const payload: EServiceDescriptorArchivedV2 = { + eservice: toEServiceV2(updatedEService), + descriptorId: archivedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDescriptorArchived", + event_version: 2, + data: payload, + log_date: new Date(), + }; + await handleMessageV2(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceDescriptorPublished", async () => { + const draftDescriptor: Descriptor = { + ...getMockDescriptor(), + interface: getMockDocument(), + state: descriptorState.draft, + }; + const eservice: EService = { + ...mockEService, + descriptors: [draftDescriptor], + }; + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const publishedDescriptor: Descriptor = { + ...draftDescriptor, + publishedAt: new Date(), + state: descriptorState.published, + }; + const updatedEService: EService = { + ...eservice, + descriptors: [publishedDescriptor], + }; + const payload: EServiceDescriptorPublishedV2 = { + eservice: toEServiceV2(updatedEService), + descriptorId: publishedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDescriptorPublished", + event_version: 2, + data: payload, + log_date: new Date(), + }; + await handleMessageV2(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceDescriptorSuspended", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + interface: getMockDocument(), + state: descriptorState.published, + publishedAt: new Date(), + }; + const eservice: EService = { + ...mockEService, + descriptors: [publishedDescriptor], + }; + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const suspendedDescriptor: Descriptor = { + ...publishedDescriptor, + suspendedAt: new Date(), + state: descriptorState.suspended, + }; + const updatedEService: EService = { + ...eservice, + descriptors: [suspendedDescriptor], + }; + const payload: EServiceDescriptorSuspendedV2 = { + eservice: toEServiceV2(updatedEService), + descriptorId: suspendedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDescriptorSuspended", + event_version: 2, + data: payload, + log_date: new Date(), + }; + await handleMessageV2(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceDescriptorInterfaceAdded", async () => { + const descriptorInterface = getMockDocument(); + const draftDescriptor: Descriptor = { + ...getMockDescriptor(), + state: descriptorState.draft, + docs: [], + }; + const eservice: EService = { + ...mockEService, + descriptors: [draftDescriptor], + }; + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + const updatedEService: EService = { + ...eservice, + descriptors: [{ ...draftDescriptor, interface: descriptorInterface }], + }; + const payload: EServiceDescriptorInterfaceAddedV2 = { + eservice: toEServiceV2(updatedEService), + descriptorId: draftDescriptor.id, + documentId: descriptorInterface.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDescriptorInterfaceAdded", + event_version: 2, + data: payload, + log_date: new Date(), + }; + await handleMessageV2(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceDescriptorDocumentAdded", async () => { + const document = getMockDocument(); + const draftDescriptor: Descriptor = { + ...getMockDescriptor(), + state: descriptorState.draft, + docs: [], + }; + const eservice: EService = { + ...mockEService, + descriptors: [draftDescriptor], + }; + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const updatedEService: EService = { + ...eservice, + descriptors: [{ ...draftDescriptor, docs: [document] }], + }; + const payload: EServiceDescriptorDocumentAddedV2 = { + eservice: toEServiceV2(updatedEService), + descriptorId: draftDescriptor.id, + documentId: document.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDescriptorDocumentAdded", + event_version: 2, + data: payload, + log_date: new Date(), + }; + await handleMessageV2(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceDescriptorInterfaceUpdated", async () => { + const descriptorInterface = getMockDocument(); + const draftDescriptor: Descriptor = { + ...getMockDescriptor(), + state: descriptorState.draft, + interface: descriptorInterface, + }; + const eservice: EService = { + ...mockEService, + descriptors: [draftDescriptor], + }; + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const updatedInterface: Document = { + ...descriptorInterface, + prettyName: "updated pretty name", + }; + const updatedEService: EService = { + ...eservice, + descriptors: [{ ...draftDescriptor, interface: updatedInterface }], + }; + const payload: EServiceDescriptorInterfaceUpdatedV2 = { + eservice: toEServiceV2(updatedEService), + descriptorId: draftDescriptor.id, + documentId: updatedInterface.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDescriptorInterfaceUpdated", + event_version: 2, + data: payload, + log_date: new Date(), + }; + await handleMessageV2(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceDescriptorDocumentUpdated", async () => { + const document = getMockDocument(); + const draftDescriptor: Descriptor = { + ...getMockDescriptor(), + state: descriptorState.draft, + docs: [document], + }; + const eservice: EService = { + ...mockEService, + descriptors: [draftDescriptor], + }; + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const updatedDocument: Document = { + ...document, + prettyName: "updated pretty name", + }; + const updatedEService: EService = { + ...eservice, + descriptors: [{ ...draftDescriptor, docs: [updatedDocument] }], + }; + const payload: EServiceDescriptorDocumentUpdatedV2 = { + eservice: toEServiceV2(updatedEService), + descriptorId: draftDescriptor.id, + documentId: document.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDescriptorDocumentUpdated", + event_version: 2, + data: payload, + log_date: new Date(), + }; + await handleMessageV2(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceDescriptorInterfaceDeleted", async () => { + const descriptorInterface = getMockDocument(); + const draftDescriptor: Descriptor = { + ...getMockDescriptor(), + state: descriptorState.draft, + interface: descriptorInterface, + }; + const eservice: EService = { + ...mockEService, + descriptors: [draftDescriptor], + }; + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const updatedEService: EService = { + ...eservice, + descriptors: [ + { ...draftDescriptor, serverUrls: [], interface: undefined }, + ], + }; + const payload: EServiceDescriptorInterfaceDeletedV2 = { + eservice: toEServiceV2(updatedEService), + descriptorId: draftDescriptor.id, + documentId: descriptorInterface.id, + }; + + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDescriptorInterfaceDeleted", + event_version: 2, + data: payload, + log_date: new Date(), + }; + await handleMessageV2(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceDescriptorDocumentDeleted", async () => { + const document = getMockDocument(); + const draftDescriptor: Descriptor = { + ...getMockDescriptor(), + state: descriptorState.draft, + docs: [document], + }; + const eservice: EService = { + ...mockEService, + descriptors: [draftDescriptor], + }; + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const updatedEService: EService = { + ...eservice, + descriptors: [{ ...draftDescriptor, docs: [] }], + }; + const payload: EServiceDescriptorDocumentDeletedV2 = { + eservice: toEServiceV2(updatedEService), + descriptorId: draftDescriptor.id, + documentId: document.id, + }; + + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDescriptorDocumentDeleted", + event_version: 2, + data: payload, + log_date: new Date(), + }; + await handleMessageV2(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceRiskAnalysisAdded", async () => { + await writeInReadmodel( + toReadModelEService(mockEService), + eservices, + 1 + ); + + const mockRiskAnalysis = getMockValidRiskAnalysis("PA"); + const updatedEService: EService = { + ...mockEService, + riskAnalysis: [mockRiskAnalysis], + }; + const payload: EServiceRiskAnalysisAddedV2 = { + eservice: toEServiceV2(updatedEService), + riskAnalysisId: mockRiskAnalysis.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceRiskAnalysisAdded", + event_version: 2, + data: payload, + log_date: new Date(), + }; + await handleMessageV2(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceRiskAnalysisUpdated", async () => { + const mockRiskAnalysis = getMockValidRiskAnalysis("PA"); + const eservice: EService = { + ...mockEService, + riskAnalysis: [mockRiskAnalysis], + }; + await writeInReadmodel( + toReadModelEService(eservice), + eservices, + 1 + ); + + const updatedRiskAnalysis: RiskAnalysis = { + ...mockRiskAnalysis, + riskAnalysisForm: { + ...mockRiskAnalysis.riskAnalysisForm, + singleAnswers: mockRiskAnalysis.riskAnalysisForm.singleAnswers.map( + (singleAnswer) => ({ + ...singleAnswer, + value: + singleAnswer.key === "purpose" ? "OTHER" : singleAnswer.value, + }) + ), + }, + }; + const updatedEService: EService = { + ...eservice, + riskAnalysis: [updatedRiskAnalysis], + }; + const payload: EServiceRiskAnalysisAddedV2 = { + eservice: toEServiceV2(updatedEService), + riskAnalysisId: mockRiskAnalysis.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceRiskAnalysisUpdated", + event_version: 2, + data: payload, + log_date: new Date(), + }; + await handleMessageV2(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + + it("EServiceRiskAnalysisDeleted", async () => { + const riskAnalysis = getMockValidRiskAnalysis("PA"); + const eservice: EService = { + ...mockEService, + riskAnalysis: [riskAnalysis], + }; + + await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const updatedEService: EService = { + ...mockEService, + riskAnalysis: [], + }; + const payload: EServiceRiskAnalysisDeletedV2 = { + eservice: toEServiceV2(updatedEService), + riskAnalysisId: riskAnalysis.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceRiskAnalysisDeleted", + event_version: 2, + data: payload, + log_date: new Date(), + }; + await handleMessageV2(message, eservices); + + // TO DO + expect(1).toBe(1); + }); + }); +}); + +export const getMockEService = (): EService => ({ + id: generateId(), + name: "eservice name", + description: "eservice description", + createdAt: new Date(), + producerId: generateId(), + technology: technology.rest, + descriptors: [], + mode: eserviceMode.deliver, + riskAnalysis: [], +}); + +export const getMockDescriptor = (): Descriptor => ({ + id: generateId(), + version: "1", + docs: [], + state: descriptorState.draft, + audience: [], + voucherLifespan: 60, + dailyCallsPerConsumer: 10, + dailyCallsTotal: 1000, + createdAt: new Date(), + serverUrls: ["pagopa.it"], + agreementApprovalPolicy: "Automatic", + attributes: { + certified: [], + verified: [], + declared: [], + }, +}); + +export const getMockDocument = (): Document => ({ + name: "fileName", + path: "filePath", + id: generateId(), + prettyName: "prettyName", + contentType: "json", + checksum: "checksum", + uploadDate: new Date(), +}); diff --git a/packages/catalog-platformstate-writer/test/tsconfig.json b/packages/catalog-platformstate-writer/test/tsconfig.json new file mode 100644 index 0000000000..379a994d81 --- /dev/null +++ b/packages/catalog-platformstate-writer/test/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../tsconfig.json", + "include": ["."] +} diff --git a/packages/catalog-platformstate-writer/test/utils.ts b/packages/catalog-platformstate-writer/test/utils.ts new file mode 100644 index 0000000000..e4e29765de --- /dev/null +++ b/packages/catalog-platformstate-writer/test/utils.ts @@ -0,0 +1,10 @@ +import { inject, afterEach } from "vitest"; +import { setupTestContainersVitest } from "pagopa-interop-commons-test"; + +export const { cleanup, readModelRepository } = setupTestContainersVitest( + inject("readModelConfig") +); + +afterEach(cleanup); + +export const eservices = readModelRepository.eservices; diff --git a/packages/catalog-platformstate-writer/test/vitestGlobalSetup.ts b/packages/catalog-platformstate-writer/test/vitestGlobalSetup.ts new file mode 100644 index 0000000000..85a4c8ea41 --- /dev/null +++ b/packages/catalog-platformstate-writer/test/vitestGlobalSetup.ts @@ -0,0 +1,3 @@ +import { setupTestContainersVitestGlobal } from "pagopa-interop-commons-test/index.js"; + +export default setupTestContainersVitestGlobal(); From 2719d63e1e208eb336fe1b5cc6aa1e23e6296c0a Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 26 Aug 2024 11:02:55 +0200 Subject: [PATCH 005/241] Update pnpm-lock file --- pnpm-lock.yaml | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4b4313e55c..c836a0a655 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -810,6 +810,70 @@ importers: specifier: 1.6.0 version: 1.6.0(@types/node@20.14.6) + packages/catalog-platformstate-writer: + dependencies: + '@protobuf-ts/runtime': + specifier: 2.9.4 + version: 2.9.4 + connection-string: + specifier: 4.4.0 + version: 4.4.0 + dotenv-flow: + specifier: 4.1.0 + version: 4.1.0 + kafka-iam-auth: + specifier: workspace:* + version: link:../kafka-iam-auth + kafkajs: + specifier: 2.2.4 + version: 2.2.4 + pagopa-interop-commons: + specifier: workspace:* + version: link:../commons + pagopa-interop-models: + specifier: workspace:* + version: link:../models + ts-pattern: + specifier: 5.2.0 + version: 5.2.0 + zod: + specifier: 3.23.8 + version: 3.23.8 + devDependencies: + '@pagopa/eslint-config': + specifier: 3.0.0 + version: 3.0.0(typescript@5.4.5) + '@types/node': + specifier: 20.14.6 + version: 20.14.6 + '@types/uuid': + specifier: 9.0.8 + version: 9.0.8 + date-fns: + specifier: 3.6.0 + version: 3.6.0 + pagopa-interop-commons-test: + specifier: workspace:* + version: link:../commons-test + prettier: + specifier: 2.8.8 + version: 2.8.8 + testcontainers: + specifier: 10.9.0 + version: 10.9.0 + ts-node: + specifier: 10.9.2 + version: 10.9.2(@types/node@20.14.6)(typescript@5.4.5) + typescript: + specifier: 5.4.5 + version: 5.4.5 + uuid: + specifier: 10.0.0 + version: 10.0.0 + vitest: + specifier: 1.6.0 + version: 1.6.0(@types/node@20.14.6) + packages/catalog-process: dependencies: '@zodios/core': From 8a90e28bf588538293233ce986d5415871e653a4 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 26 Aug 2024 11:07:45 +0200 Subject: [PATCH 006/241] Fix config --- .../src/config/config.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/config/config.ts b/packages/catalog-platformstate-writer/src/config/config.ts index 76cbaf6b8a..a2a4af58b4 100644 --- a/packages/catalog-platformstate-writer/src/config/config.ts +++ b/packages/catalog-platformstate-writer/src/config/config.ts @@ -1,15 +1,15 @@ import { CatalogTopicConfig, - ReadModelWriterConfig, + PlatformStateWriterConfig, } from "pagopa-interop-commons"; import { z } from "zod"; -export const CatalogReadModelWriterConfig = - ReadModelWriterConfig.and(CatalogTopicConfig); +export const CatalogPlatformStateWriterConfig = + PlatformStateWriterConfig.and(CatalogTopicConfig); -export type CatalogReadModelWriterConfig = z.infer< - typeof CatalogReadModelWriterConfig +export type CatalogPlatformStateWriterConfig = z.infer< + typeof CatalogPlatformStateWriterConfig >; -export const config: CatalogReadModelWriterConfig = - CatalogReadModelWriterConfig.parse(process.env); +export const config: CatalogPlatformStateWriterConfig = + CatalogPlatformStateWriterConfig.parse(process.env); From 5aee6c39b2765f9b4b1e0b4a7ade56168184c43b Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 26 Aug 2024 13:00:03 +0200 Subject: [PATCH 007/241] Update docker-compose file --- docker/docker-compose.yml | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 8554d2c15b..832df08647 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -54,6 +54,28 @@ services: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: example + token-generation-readmodel: + command: "-jar DynamoDBLocal.jar -inMemory -sharedDb" + image: "amazon/dynamodb-local:latest" + ports: + - 8085:8000 + + token-generation-readmodel-db-init: + depends_on: + - token-generation-readmodel + image: amazon/aws-cli + environment: + AWS_ACCESS_KEY_ID: keyid + AWS_SECRET_ACCESS_KEY: key + command: dynamodb create-table + --table-name PlatformStates + --attribute-definitions + AttributeName=PK,AttributeType=S + --key-schema + AttributeName=PK,KeyType=HASH + --billing-mode PAY_PER_REQUEST + --endpoint-url http://token-generation-readmodel:8000 --region eu-central-1 + # Mongo Express is a web-based MongoDB admin interface, included for convenience mongo-express: image: mongo-express:1.0.2-20 @@ -162,7 +184,7 @@ services: KMS_REGION: eu-central-1 volumes: - ./local-kms-seed/seed.yaml:/init/seed.yaml - + # HTTP server to simulate well-known JWKS endpoint jwks: image: nginx:1.27.0 @@ -170,4 +192,3 @@ services: - 4500:80 volumes: - ./local-kms-seed/jwks.json:/usr/share/nginx/html/jwks.json - From 1e91b33dc518cbf88eff43a2e5cfed989784e640 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 26 Aug 2024 14:13:45 +0200 Subject: [PATCH 008/241] Temporary fix --- .../src/consumerServiceV1.ts | 3 +-- .../src/consumerServiceV2.ts | 3 +-- packages/catalog-platformstate-writer/src/index.ts | 12 ++++-------- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index 0dde73f476..64d34d28f0 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -1,10 +1,9 @@ -import { EServiceCollection } from "pagopa-interop-commons"; import { match } from "ts-pattern"; import { EServiceEventEnvelopeV1 } from "pagopa-interop-models"; export async function handleMessageV1( message: EServiceEventEnvelopeV1, - _eservices: EServiceCollection // TO DO dynamoDB table + _dynamodbTable: unknown // TO DO dynamoDB table ): Promise { await match(message) .with( diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index 9d8041f78a..08c92c2bc1 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -1,10 +1,9 @@ -import { EServiceCollection } from "pagopa-interop-commons"; import { EServiceEventEnvelopeV2 } from "pagopa-interop-models"; import { match } from "ts-pattern"; export async function handleMessageV2( message: EServiceEventEnvelopeV2, - _eservices: EServiceCollection // TO DO dynamoDB table + _dynamodbTable: unknown // TO DO dynamoDB table ): Promise { await match(message) .with( diff --git a/packages/catalog-platformstate-writer/src/index.ts b/packages/catalog-platformstate-writer/src/index.ts index 07aaccfca0..59ff81f04c 100644 --- a/packages/catalog-platformstate-writer/src/index.ts +++ b/packages/catalog-platformstate-writer/src/index.ts @@ -1,9 +1,5 @@ import { EachMessagePayload } from "kafkajs"; -import { - logger, - ReadModelRepository, - decodeKafkaMessage, -} from "pagopa-interop-commons"; +import { logger, decodeKafkaMessage } from "pagopa-interop-commons"; import { runConsumer } from "kafka-iam-auth"; import { EServiceEvent } from "pagopa-interop-models"; import { match } from "ts-pattern"; @@ -11,7 +7,7 @@ import { handleMessageV1 } from "./consumerServiceV1.js"; import { handleMessageV2 } from "./consumerServiceV2.js"; import { config } from "./config/config.js"; -const { eservices } = ReadModelRepository.init(config); +const dynamodbTable = undefined; // TO DO async function processMessage({ message, @@ -28,8 +24,8 @@ async function processMessage({ }); await match(decodedMessage) - .with({ event_version: 1 }, (msg) => handleMessageV1(msg, eservices)) - .with({ event_version: 2 }, (msg) => handleMessageV2(msg, eservices)) + .with({ event_version: 1 }, (msg) => handleMessageV1(msg, dynamodbTable)) + .with({ event_version: 2 }, (msg) => handleMessageV2(msg, dynamodbTable)) .exhaustive(); loggerInstance.info( From 4fa2d3bb25098c9863396a6151841ec07112d4e0 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 26 Aug 2024 15:05:07 +0200 Subject: [PATCH 009/241] Add types for new tables --- packages/models/src/index.ts | 3 + .../platform-states-entry.ts | 63 +++++++++++++++++++ .../token-generation-states-entry.ts | 38 +++++++++++ 3 files changed, 104 insertions(+) create mode 100644 packages/models/src/token-generation-readmodel/platform-states-entry.ts create mode 100644 packages/models/src/token-generation-readmodel/token-generation-states-entry.ts diff --git a/packages/models/src/index.ts b/packages/models/src/index.ts index a2771baef6..121901ace4 100644 --- a/packages/models/src/index.ts +++ b/packages/models/src/index.ts @@ -52,6 +52,9 @@ export * from "./authorization/authorizationReadModelAdapter.js"; export * from "./user/user.js"; +export * from "./token-generation-readmodel/platform-states-entry.js"; +export * from "./token-generation-readmodel/token-generation-states-entry.js"; + // Protobuf export * from "./protobuf/protobuf.js"; diff --git a/packages/models/src/token-generation-readmodel/platform-states-entry.ts b/packages/models/src/token-generation-readmodel/platform-states-entry.ts new file mode 100644 index 0000000000..b91e9d89c4 --- /dev/null +++ b/packages/models/src/token-generation-readmodel/platform-states-entry.ts @@ -0,0 +1,63 @@ +import { z } from "zod"; +import { + EServiceId, + PurposeId, + PurposeVersionId, + TenantId, +} from "../brandedIds.js"; + +export const itemState = { + active: "ACTIVE", + inactive: "INACTIVE", +} as const; +export const ItemState = z.enum([ + Object.values(itemState)[0], + ...Object.values(itemState).slice(1), +]); +export type ItemState = z.infer; + +const PlatformStatesBaseEntry = z.object({ + PK: z.string(), + state: ItemState, +}); +type PlatformStatesBaseEntry = z.infer; + +export const PlatformStatesCatalogEntry = + PlatformStatesBaseEntry && + z.object({ + descriptorAudience: z.string(), + }); +export type PlatformStatesCatalogEntry = z.infer< + typeof PlatformStatesCatalogEntry +>; + +export const PlatformStatesPurposeEntry = + PlatformStatesBaseEntry && + z.object({ + purposeVersionId: PurposeVersionId, + purposeEserviceId: EServiceId, + purposeConsumerId: TenantId, + }); +export type PlatformStatesPurposeEntry = z.infer< + typeof PlatformStatesPurposeEntry +>; + +export const PlatformStatesAgreementEntry = + PlatformStatesBaseEntry && + z.object({ + GSIPK_consumerId_eserviceId: z.string(), + GSISK_agreementTimestamp: z.string(), + agreementDescriptorId: z.string(), + }); +export type PlatformStatesAgreementEntry = z.infer< + typeof PlatformStatesAgreementEntry +>; + +export const PlatformStatesClientEntry = + PlatformStatesBaseEntry && + z.object({ + clientPurposesIds: z.array(PurposeId), + }); +export type PlatformStatesClientEntry = z.infer< + typeof PlatformStatesClientEntry +>; diff --git a/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts b/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts new file mode 100644 index 0000000000..55444c51b5 --- /dev/null +++ b/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts @@ -0,0 +1,38 @@ +import { z } from "zod"; +import { ClientKind } from "../authorization/client.js"; +import { PurposeVersionId } from "../brandedIds.js"; +import { ItemState } from "./platform-states-entry.js"; + +const TokenGenerationStatesBaseEntry = z.object({ + PK: z.string(), + consumerId: z.string(), + clientKind: ClientKind, + publicKey: z.string(), + GSIPK_clientId: z.string(), + GSIPK_kid: z.string(), + GSIPK_clientId_purposeId: z.string(), +}); +type TokenGenerationStatesBaseEntry = z.infer< + typeof TokenGenerationStatesBaseEntry +>; + +export const TokenGenerationStatesClientPurposeEntry = + TokenGenerationStatesBaseEntry && + z.object({ + GSIPK_consumerId_eserviceId: z.string(), + agreementState: ItemState, + GSIPK_eserviceId_descriptorId: z.string(), + descriptorState: ItemState, + descriptorAudience: z.string(), + GSIPK_purposeId: z.string(), + purposeState: ItemState, + purposeVersionId: PurposeVersionId, + }); +export type TokenGenerationStatesClientPurposeEntry = z.infer< + typeof TokenGenerationStatesClientPurposeEntry +>; + +export const TokenGenerationStatesClientEntry = TokenGenerationStatesBaseEntry; +export type TokenGenerationStatesClientEntry = z.infer< + typeof TokenGenerationStatesClientPurposeEntry +>; From d2960fc170795a3fa6676856c50a1a5d242f9db8 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 27 Aug 2024 12:46:31 +0200 Subject: [PATCH 010/241] Work in progress --- packages/catalog-platformstate-writer/.env | 6 +- .../aws.config.local | 4 + .../catalog-platformstate-writer/package.json | 2 + .../src/consumerServiceV1.ts | 3 +- .../src/consumerServiceV2.ts | 3 +- .../catalog-platformstate-writer/src/index.ts | 12 +- .../catalog-platformstate-writer/src/utils.ts | 96 ++ .../test/utils.ts | 13 +- packages/commons/src/config/index.ts | 1 + .../tokenGenerationReadmodelDbConfig.ts | 18 +- .../platform-states-entry.ts | 48 +- pnpm-lock.yaml | 861 +++++++++++++++++- 12 files changed, 1003 insertions(+), 64 deletions(-) create mode 100644 packages/catalog-platformstate-writer/aws.config.local create mode 100644 packages/catalog-platformstate-writer/src/utils.ts diff --git a/packages/catalog-platformstate-writer/.env b/packages/catalog-platformstate-writer/.env index 05c1cc3684..1f9a570471 100644 --- a/packages/catalog-platformstate-writer/.env +++ b/packages/catalog-platformstate-writer/.env @@ -5,5 +5,9 @@ KAFKA_GROUP_ID="catalog-group" KAFKA_BROKERS="localhost:9092" KAFKA_DISABLE_AWS_IAM_AUTH="true" CATALOG_TOPIC="event-store.catalog.events" -// TO DO add config for dynamoDB +AWS_CONFIG_FILE=aws.config.local +TOKEN_GENERATION_READMODEL_HOST="localhost" +TOKEN_GENERATION_READMODEL_PORT=8085 +TOKEN_GENERATION_READMODEL_TABLE_NAME="PlatformStates" + AWS_REGION="eu-central-1" diff --git a/packages/catalog-platformstate-writer/aws.config.local b/packages/catalog-platformstate-writer/aws.config.local new file mode 100644 index 0000000000..c2cb4f4115 --- /dev/null +++ b/packages/catalog-platformstate-writer/aws.config.local @@ -0,0 +1,4 @@ +[default] +aws_access_key_id=test-aws-key +aws_secret_access_key=test-aws-secret +region=eu-central-1 diff --git a/packages/catalog-platformstate-writer/package.json b/packages/catalog-platformstate-writer/package.json index 86d977c4be..24d7c7ea5d 100644 --- a/packages/catalog-platformstate-writer/package.json +++ b/packages/catalog-platformstate-writer/package.json @@ -33,6 +33,8 @@ "vitest": "1.6.0" }, "dependencies": { + "@aws-sdk/client-dynamodb": "3.637.0", + "@aws-sdk/util-dynamodb": "3.637.0", "@protobuf-ts/runtime": "2.9.4", "connection-string": "4.4.0", "dotenv-flow": "4.1.0", diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index 64d34d28f0..b85992c6cd 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -1,9 +1,10 @@ import { match } from "ts-pattern"; import { EServiceEventEnvelopeV1 } from "pagopa-interop-models"; +import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; export async function handleMessageV1( message: EServiceEventEnvelopeV1, - _dynamodbTable: unknown // TO DO dynamoDB table + _dynamoDBClient: DynamoDBClient ): Promise { await match(message) .with( diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index 08c92c2bc1..6ad2e6b799 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -1,9 +1,10 @@ +import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; import { EServiceEventEnvelopeV2 } from "pagopa-interop-models"; import { match } from "ts-pattern"; export async function handleMessageV2( message: EServiceEventEnvelopeV2, - _dynamodbTable: unknown // TO DO dynamoDB table + _dynamoDBClient: DynamoDBClient ): Promise { await match(message) .with( diff --git a/packages/catalog-platformstate-writer/src/index.ts b/packages/catalog-platformstate-writer/src/index.ts index 59ff81f04c..642c3508d3 100644 --- a/packages/catalog-platformstate-writer/src/index.ts +++ b/packages/catalog-platformstate-writer/src/index.ts @@ -3,12 +3,16 @@ import { logger, decodeKafkaMessage } from "pagopa-interop-commons"; import { runConsumer } from "kafka-iam-auth"; import { EServiceEvent } from "pagopa-interop-models"; import { match } from "ts-pattern"; +import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; import { handleMessageV1 } from "./consumerServiceV1.js"; import { handleMessageV2 } from "./consumerServiceV2.js"; import { config } from "./config/config.js"; -const dynamodbTable = undefined; // TO DO - +const dynamoDBClient = new DynamoDBClient({ + credentials: { accessKeyId: "key", secretAccessKey: "secret" }, + region: "eu-central-1", + endpoint: `http://${config.tokenGenerationReadModelDbHost}:${config.tokenGenerationReadModelDbPort}`, +}); async function processMessage({ message, partition, @@ -24,8 +28,8 @@ async function processMessage({ }); await match(decodedMessage) - .with({ event_version: 1 }, (msg) => handleMessageV1(msg, dynamodbTable)) - .with({ event_version: 2 }, (msg) => handleMessageV2(msg, dynamodbTable)) + .with({ event_version: 1 }, (msg) => handleMessageV1(msg, dynamoDBClient)) + .with({ event_version: 2 }, (msg) => handleMessageV2(msg, dynamoDBClient)) .exhaustive(); loggerInstance.info( diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts new file mode 100644 index 0000000000..8cdcf8d13b --- /dev/null +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -0,0 +1,96 @@ +import { + genericInternalError, + PlatformStatesCatalogEntry, +} from "pagopa-interop-models"; +import { + DeleteItemCommand, + DeleteItemInput, + DynamoDBClient, + GetItemCommand, + GetItemCommandOutput, + GetItemInput, + PutItemCommand, + PutItemInput, + ScanCommand, + ScanCommandOutput, + ScanInput, +} from "@aws-sdk/client-dynamodb"; +import { unmarshall } from "@aws-sdk/util-dynamodb"; +import { config } from "./config/config.js"; + +export const writeCatalogEntry = async ( + catalogEntry: PlatformStatesCatalogEntry, + dynamoDBClient: DynamoDBClient +): Promise => { + const input: PutItemInput = { + Item: { + PK: { + S: catalogEntry.PK, + }, + state: { + S: catalogEntry.state, + }, + descriptorAudience: { + S: catalogEntry.descriptorAudience, + }, + }, + TableName: config.tokenGenerationReadModelTableName, + }; + const command = new PutItemCommand(input); + await dynamoDBClient.send(command); +}; + +export const readCatalogEntry = async ( + primaryKey: string, + dynamoDBClient: DynamoDBClient +): Promise => { + const input: GetItemInput = { + Key: { + PK: { S: primaryKey }, + }, + TableName: config.tokenGenerationReadModelTableName, + }; + const command = new GetItemCommand(input); + const data: GetItemCommandOutput = await dynamoDBClient.send(command); + + if (!data.Item) { + return undefined; + } else { + const unmarshalled = unmarshall(data.Item); + const catalogEntry = PlatformStatesCatalogEntry.safeParse(unmarshalled); + + if (!catalogEntry.success) { + throw genericInternalError( + `Unable to parse catalog entry item: result ${JSON.stringify( + catalogEntry + )} - data ${JSON.stringify(data)} ` + ); + } + return catalogEntry.data; + } +}; + +export const deleteCatalogEntry = async ( + primaryKey: string, + dynamoDBClient: DynamoDBClient +): Promise => { + const input: DeleteItemInput = { + Key: { + PK: { S: primaryKey }, + }, + TableName: config.tokenGenerationReadModelTableName, + }; + const command = new DeleteItemCommand(input); + await dynamoDBClient.send(command); +}; + +export const readAllItems = async ( + dynamoDBClient: DynamoDBClient +): Promise => { + const readInput: ScanInput = { + TableName: config.tokenGenerationReadModelTableName, + }; + const commandQuery = new ScanCommand(readInput); + const read: ScanCommandOutput = await dynamoDBClient.send(commandQuery); + return read; +}; diff --git a/packages/catalog-platformstate-writer/test/utils.ts b/packages/catalog-platformstate-writer/test/utils.ts index e4e29765de..0fcfb8585d 100644 --- a/packages/catalog-platformstate-writer/test/utils.ts +++ b/packages/catalog-platformstate-writer/test/utils.ts @@ -1,10 +1,7 @@ -import { inject, afterEach } from "vitest"; -import { setupTestContainersVitest } from "pagopa-interop-commons-test"; +// export const { cleanup, readModelRepository } = setupTestContainersVitest( +// inject("tokenGenerationReadModelConfig") +// ); -export const { cleanup, readModelRepository } = setupTestContainersVitest( - inject("readModelConfig") -); +// afterEach(cleanup); -afterEach(cleanup); - -export const eservices = readModelRepository.eservices; +// export const eservices = readModelRepository.eservices; diff --git a/packages/commons/src/config/index.ts b/packages/commons/src/config/index.ts index 6c0727f74b..02cd685792 100644 --- a/packages/commons/src/config/index.ts +++ b/packages/commons/src/config/index.ts @@ -5,6 +5,7 @@ export * from "./consumerServiceConfig.js"; export * from "./producerServiceConfig.js"; export * from "./kafkaConfig.js"; export * from "./readmodelDbConfig.js"; +export * from "./tokenGenerationReadmodelDbConfig.js"; export * from "./eventStoreConfig.js"; export * from "./fileManagerConfig.js"; export * from "./kafkaTopicConfig.js"; diff --git a/packages/commons/src/config/tokenGenerationReadmodelDbConfig.ts b/packages/commons/src/config/tokenGenerationReadmodelDbConfig.ts index 64821e5fc6..e95ed92942 100644 --- a/packages/commons/src/config/tokenGenerationReadmodelDbConfig.ts +++ b/packages/commons/src/config/tokenGenerationReadmodelDbConfig.ts @@ -2,20 +2,14 @@ import { z } from "zod"; export const TokenGenerationReadModelDbConfig = z .object({ - TOKEN_GENERATION_READMODEL_DB_HOST: z.string(), - TOKEN_GENERATION_READMODEL_DB_NAME: z.string(), - TOKEN_GENERATION_READMODEL_DB_USERNAME: z.string(), - TOKEN_GENERATION_READMODEL_DB_PASSWORD: z.string(), - TOKEN_GENERATION_READMODEL_DB_PORT: z.coerce.number().min(1001), + TOKEN_GENERATION_READMODEL_HOST: z.string(), + TOKEN_GENERATION_READMODEL_PORT: z.coerce.number().min(1001), + TOKEN_GENERATION_READMODEL_TABLE_NAME: z.string(), }) .transform((c) => ({ - tokenGenerationReadModelDbHost: c.TOKEN_GENERATION_READMODEL_DB_HOST, - tokenGenerationReadModelDbName: c.TOKEN_GENERATION_READMODEL_DB_NAME, - tokenGenerationReadModelDbUsername: - c.TOKEN_GENERATION_READMODEL_DB_USERNAME, - tokenGenerationReadModelDbPassword: - c.TOKEN_GENERATION_READMODEL_DB_PASSWORD, - tokenGenerationReadModelDbPort: c.TOKEN_GENERATION_READMODEL_DB_PORT, + tokenGenerationReadModelDbHost: c.TOKEN_GENERATION_READMODEL_HOST, + tokenGenerationReadModelDbPort: c.TOKEN_GENERATION_READMODEL_PORT, + tokenGenerationReadModelTableName: c.TOKEN_GENERATION_READMODEL_TABLE_NAME, })); export type TokenGenerationReadModelDbConfig = z.infer< diff --git a/packages/models/src/token-generation-readmodel/platform-states-entry.ts b/packages/models/src/token-generation-readmodel/platform-states-entry.ts index b91e9d89c4..bbdc6d106f 100644 --- a/packages/models/src/token-generation-readmodel/platform-states-entry.ts +++ b/packages/models/src/token-generation-readmodel/platform-states-entry.ts @@ -16,48 +16,48 @@ export const ItemState = z.enum([ ]); export type ItemState = z.infer; +/* +type PrimaryKeyPlatformStates = + | `ESERVICEDESCRIPTOR#${EServiceId}#${DescriptorId}` + | `AGREEMENT#${AgreementId}` + | `PURPOSE#${PurposeId}` + | `CLIENT#${ClientId}`; +*/ + const PlatformStatesBaseEntry = z.object({ PK: z.string(), state: ItemState, }); type PlatformStatesBaseEntry = z.infer; -export const PlatformStatesCatalogEntry = - PlatformStatesBaseEntry && - z.object({ - descriptorAudience: z.string(), - }); +export const PlatformStatesCatalogEntry = PlatformStatesBaseEntry.extend({ + descriptorAudience: z.string(), +}); export type PlatformStatesCatalogEntry = z.infer< typeof PlatformStatesCatalogEntry >; -export const PlatformStatesPurposeEntry = - PlatformStatesBaseEntry && - z.object({ - purposeVersionId: PurposeVersionId, - purposeEserviceId: EServiceId, - purposeConsumerId: TenantId, - }); +export const PlatformStatesPurposeEntry = PlatformStatesBaseEntry.extend({ + purposeVersionId: PurposeVersionId, + purposeEserviceId: EServiceId, + purposeConsumerId: TenantId, +}); export type PlatformStatesPurposeEntry = z.infer< typeof PlatformStatesPurposeEntry >; -export const PlatformStatesAgreementEntry = - PlatformStatesBaseEntry && - z.object({ - GSIPK_consumerId_eserviceId: z.string(), - GSISK_agreementTimestamp: z.string(), - agreementDescriptorId: z.string(), - }); +export const PlatformStatesAgreementEntry = PlatformStatesBaseEntry.extend({ + GSIPK_consumerId_eserviceId: z.string(), + GSISK_agreementTimestamp: z.string(), + agreementDescriptorId: z.string(), +}); export type PlatformStatesAgreementEntry = z.infer< typeof PlatformStatesAgreementEntry >; -export const PlatformStatesClientEntry = - PlatformStatesBaseEntry && - z.object({ - clientPurposesIds: z.array(PurposeId), - }); +export const PlatformStatesClientEntry = PlatformStatesBaseEntry.extend({ + clientPurposesIds: z.array(PurposeId), +}); export type PlatformStatesClientEntry = z.infer< typeof PlatformStatesClientEntry >; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c836a0a655..45285d2c77 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -812,6 +812,12 @@ importers: packages/catalog-platformstate-writer: dependencies: + '@aws-sdk/client-dynamodb': + specifier: 3.637.0 + version: 3.637.0 + '@aws-sdk/util-dynamodb': + specifier: 3.637.0 + version: 3.637.0(@aws-sdk/client-dynamodb@3.637.0) '@protobuf-ts/runtime': specifier: 2.9.4 version: 2.9.4 @@ -1422,7 +1428,7 @@ importers: dependencies: aws-msk-iam-sasl-signer-js: specifier: 1.0.0 - version: 1.0.0(@aws-sdk/client-sso-oidc@3.609.0) + version: 1.0.0(@aws-sdk/client-sso-oidc@3.637.0) kafkajs: specifier: 2.2.4 version: 2.2.4 @@ -2066,7 +2072,7 @@ packages: engines: {node: '>=16.0.0'} dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.598.0 + '@aws-sdk/types': 3.609.0 tslib: 2.6.3 dev: false @@ -2074,7 +2080,7 @@ packages: resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.598.0 + '@aws-sdk/types': 3.609.0 tslib: 2.6.3 dev: false @@ -2095,7 +2101,7 @@ packages: '@aws-crypto/sha256-js': 5.2.0 '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.598.0 + '@aws-sdk/types': 3.609.0 '@aws-sdk/util-locate-window': 3.568.0 '@smithy/util-utf8': 2.3.0 tslib: 2.6.3 @@ -2114,7 +2120,7 @@ packages: engines: {node: '>=16.0.0'} dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.598.0 + '@aws-sdk/types': 3.609.0 tslib: 2.6.3 dev: false @@ -2135,7 +2141,7 @@ packages: /@aws-crypto/util@5.2.0: resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} dependencies: - '@aws-sdk/types': 3.598.0 + '@aws-sdk/types': 3.609.0 '@smithy/util-utf8': 2.3.0 tslib: 2.6.3 dev: false @@ -2189,6 +2195,58 @@ packages: - aws-crt dev: false + /@aws-sdk/client-dynamodb@3.637.0: + resolution: {integrity: sha512-zUneT0yLgJjC69yry2fgYVWkv68OeV3amWaDXHirA8yJgygyc7tBLo+sQmtHczmKt8dBD9bU3OWpbAbtpF9Esw==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/client-sso-oidc': 3.637.0(@aws-sdk/client-sts@3.637.0) + '@aws-sdk/client-sts': 3.637.0 + '@aws-sdk/core': 3.635.0 + '@aws-sdk/credential-provider-node': 3.637.0(@aws-sdk/client-sso-oidc@3.637.0)(@aws-sdk/client-sts@3.637.0) + '@aws-sdk/middleware-endpoint-discovery': 3.620.0 + '@aws-sdk/middleware-host-header': 3.620.0 + '@aws-sdk/middleware-logger': 3.609.0 + '@aws-sdk/middleware-recursion-detection': 3.620.0 + '@aws-sdk/middleware-user-agent': 3.637.0 + '@aws-sdk/region-config-resolver': 3.614.0 + '@aws-sdk/types': 3.609.0 + '@aws-sdk/util-endpoints': 3.637.0 + '@aws-sdk/util-user-agent-browser': 3.609.0 + '@aws-sdk/util-user-agent-node': 3.614.0 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/hash-node': 3.0.3 + '@smithy/invalid-dependency': 3.0.3 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 + '@smithy/util-utf8': 3.0.0 + '@smithy/util-waiter': 3.1.2 + tslib: 2.6.3 + uuid: 9.0.1 + transitivePeerDependencies: + - aws-crt + dev: false + /@aws-sdk/client-kms@3.600.0: resolution: {integrity: sha512-m1o8aiVrVjExw6O+8JszXV3hr8sCyXKOLq1WCwWJqYF6Uf4vCf8iTYISQB3skbKUnBJm4SxVA82iViGAtWB7JA==} engines: {node: '>=16.0.0'} @@ -2454,6 +2512,106 @@ packages: - aws-crt dev: false + /@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.609.0): + resolution: {integrity: sha512-27bHALN6Qb6m6KZmPvRieJ/QRlj1lyac/GT2Rn5kJpre8Mpp+yxrtvp3h9PjNBty4lCeFEENfY4dGNSozBuBcw==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sts': ^3.637.0 + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/client-sts': 3.609.0 + '@aws-sdk/core': 3.635.0 + '@aws-sdk/credential-provider-node': 3.637.0(@aws-sdk/client-sso-oidc@3.637.0)(@aws-sdk/client-sts@3.609.0) + '@aws-sdk/middleware-host-header': 3.620.0 + '@aws-sdk/middleware-logger': 3.609.0 + '@aws-sdk/middleware-recursion-detection': 3.620.0 + '@aws-sdk/middleware-user-agent': 3.637.0 + '@aws-sdk/region-config-resolver': 3.614.0 + '@aws-sdk/types': 3.609.0 + '@aws-sdk/util-endpoints': 3.637.0 + '@aws-sdk/util-user-agent-browser': 3.609.0 + '@aws-sdk/util-user-agent-node': 3.614.0 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/hash-node': 3.0.3 + '@smithy/invalid-dependency': 3.0.3 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.3 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0): + resolution: {integrity: sha512-27bHALN6Qb6m6KZmPvRieJ/QRlj1lyac/GT2Rn5kJpre8Mpp+yxrtvp3h9PjNBty4lCeFEENfY4dGNSozBuBcw==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sts': ^3.637.0 + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/client-sts': 3.637.0 + '@aws-sdk/core': 3.635.0 + '@aws-sdk/credential-provider-node': 3.637.0(@aws-sdk/client-sso-oidc@3.637.0)(@aws-sdk/client-sts@3.637.0) + '@aws-sdk/middleware-host-header': 3.620.0 + '@aws-sdk/middleware-logger': 3.609.0 + '@aws-sdk/middleware-recursion-detection': 3.620.0 + '@aws-sdk/middleware-user-agent': 3.637.0 + '@aws-sdk/region-config-resolver': 3.614.0 + '@aws-sdk/types': 3.609.0 + '@aws-sdk/util-endpoints': 3.637.0 + '@aws-sdk/util-user-agent-browser': 3.609.0 + '@aws-sdk/util-user-agent-node': 3.614.0 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/hash-node': 3.0.3 + '@smithy/invalid-dependency': 3.0.3 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.3 + transitivePeerDependencies: + - aws-crt + dev: false + /@aws-sdk/client-sso@3.598.0: resolution: {integrity: sha512-nOI5lqPYa+YZlrrzwAJywJSw3MKVjvu6Ge2fCqQUNYMfxFB0NAaDFnl0EPjXi+sEbtCuz/uWE77poHbqiZ+7Iw==} engines: {node: '>=16.0.0'} @@ -2546,6 +2704,52 @@ packages: - aws-crt dev: false + /@aws-sdk/client-sso@3.637.0: + resolution: {integrity: sha512-+KjLvgX5yJYROWo3TQuwBJlHCY0zz9PsLuEolmXQn0BVK1L/m9GteZHtd+rEdAoDGBpE0Xqjy1oz5+SmtsaRUw==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.635.0 + '@aws-sdk/middleware-host-header': 3.620.0 + '@aws-sdk/middleware-logger': 3.609.0 + '@aws-sdk/middleware-recursion-detection': 3.620.0 + '@aws-sdk/middleware-user-agent': 3.637.0 + '@aws-sdk/region-config-resolver': 3.614.0 + '@aws-sdk/types': 3.609.0 + '@aws-sdk/util-endpoints': 3.637.0 + '@aws-sdk/util-user-agent-browser': 3.609.0 + '@aws-sdk/util-user-agent-node': 3.614.0 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/hash-node': 3.0.3 + '@smithy/invalid-dependency': 3.0.3 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.3 + transitivePeerDependencies: + - aws-crt + dev: false + /@aws-sdk/client-sts@3.600.0: resolution: {integrity: sha512-KQG97B7LvTtTiGmjlrG1LRAY8wUvCQzrmZVV5bjrJ/1oXAU7DITYwVbSJeX9NWg6hDuSk0VE3MFwIXS2SvfLIA==} engines: {node: '>=16.0.0'} @@ -2642,6 +2846,54 @@ packages: - aws-crt dev: false + /@aws-sdk/client-sts@3.637.0: + resolution: {integrity: sha512-xUi7x4qDubtA8QREtlblPuAcn91GS/09YVEY/RwU7xCY0aqGuFwgszAANlha4OUIqva8oVj2WO4gJuG+iaSnhw==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/client-sso-oidc': 3.637.0(@aws-sdk/client-sts@3.637.0) + '@aws-sdk/core': 3.635.0 + '@aws-sdk/credential-provider-node': 3.637.0(@aws-sdk/client-sso-oidc@3.637.0)(@aws-sdk/client-sts@3.637.0) + '@aws-sdk/middleware-host-header': 3.620.0 + '@aws-sdk/middleware-logger': 3.609.0 + '@aws-sdk/middleware-recursion-detection': 3.620.0 + '@aws-sdk/middleware-user-agent': 3.637.0 + '@aws-sdk/region-config-resolver': 3.614.0 + '@aws-sdk/types': 3.609.0 + '@aws-sdk/util-endpoints': 3.637.0 + '@aws-sdk/util-user-agent-browser': 3.609.0 + '@aws-sdk/util-user-agent-node': 3.614.0 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/hash-node': 3.0.3 + '@smithy/invalid-dependency': 3.0.3 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.3 + transitivePeerDependencies: + - aws-crt + dev: false + /@aws-sdk/core@3.598.0: resolution: {integrity: sha512-HaSjt7puO5Cc7cOlrXFCW0rtA0BM9lvzjl56x0A20Pt+0wxXGeTOZZOkXQIepbrFkV2e/HYukuT9e99vXDm59g==} engines: {node: '>=16.0.0'} @@ -2668,6 +2920,22 @@ packages: tslib: 2.6.3 dev: false + /@aws-sdk/core@3.635.0: + resolution: {integrity: sha512-i1x/E/sgA+liUE1XJ7rj1dhyXpAKO1UKFUcTTHXok2ARjWTvszHnSXMOsB77aPbmn0fUp1JTx2kHUAZ1LVt5Bg==} + engines: {node: '>=16.0.0'} + dependencies: + '@smithy/core': 2.4.0 + '@smithy/node-config-provider': 3.1.4 + '@smithy/property-provider': 3.1.3 + '@smithy/protocol-http': 4.1.0 + '@smithy/signature-v4': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/util-middleware': 3.0.3 + fast-xml-parser: 4.4.1 + tslib: 2.6.3 + dev: false + /@aws-sdk/credential-provider-cognito-identity@3.609.0: resolution: {integrity: sha512-BqrpAXRr64dQ/uZsRB2wViGKTkVRlfp8Q+Zd7Bc8Ikk+YXjPtl+IyWXKtdKQ3LBO255KwAcPmra5oFC+2R1GOQ==} engines: {node: '>=16.0.0'} @@ -2701,6 +2969,16 @@ packages: tslib: 2.6.3 dev: false + /@aws-sdk/credential-provider-env@3.620.1: + resolution: {integrity: sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-sdk/types': 3.609.0 + '@smithy/property-provider': 3.1.3 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + dev: false + /@aws-sdk/credential-provider-http@3.598.0: resolution: {integrity: sha512-N7cIafi4HVlQvEgvZSo1G4T9qb/JMLGMdBsDCT5XkeJrF0aptQWzTFH0jIdZcLrMYvzPcuEyO3yCBe6cy/ba0g==} engines: {node: '>=16.0.0'} @@ -2731,6 +3009,21 @@ packages: tslib: 2.6.3 dev: false + /@aws-sdk/credential-provider-http@3.635.0: + resolution: {integrity: sha512-iJyRgEjOCQlBMXqtwPLIKYc7Bsc6nqjrZybdMDenPDa+kmLg7xh8LxHsu9088e+2/wtLicE34FsJJIfzu3L82g==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-sdk/types': 3.609.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/property-provider': 3.1.3 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/util-stream': 3.1.3 + tslib: 2.6.3 + dev: false + /@aws-sdk/credential-provider-ini@3.598.0(@aws-sdk/client-sso-oidc@3.600.0)(@aws-sdk/client-sts@3.600.0): resolution: {integrity: sha512-/ppcIVUbRwDIwJDoYfp90X3+AuJo2mvE52Y1t2VSrvUovYn6N4v95/vXj6LS8CNDhz2jvEJYmu+0cTMHdhI6eA==} engines: {node: '>=16.0.0'} @@ -2777,6 +3070,75 @@ packages: - aws-crt dev: false + /@aws-sdk/credential-provider-ini@3.609.0(@aws-sdk/client-sso-oidc@3.637.0)(@aws-sdk/client-sts@3.609.0): + resolution: {integrity: sha512-hwaBfXuBTv6/eAdEsDfGcteYUW6Km7lvvubbxEdxIuJNF3vswR7RMGIXaEC37hhPkTTgd3H0TONammhwZIfkog==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sts': ^3.609.0 + dependencies: + '@aws-sdk/client-sts': 3.609.0 + '@aws-sdk/credential-provider-env': 3.609.0 + '@aws-sdk/credential-provider-http': 3.609.0 + '@aws-sdk/credential-provider-process': 3.609.0 + '@aws-sdk/credential-provider-sso': 3.609.0(@aws-sdk/client-sso-oidc@3.637.0) + '@aws-sdk/credential-provider-web-identity': 3.609.0(@aws-sdk/client-sts@3.609.0) + '@aws-sdk/types': 3.609.0 + '@smithy/credential-provider-imds': 3.1.3 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + dev: false + + /@aws-sdk/credential-provider-ini@3.637.0(@aws-sdk/client-sso-oidc@3.637.0)(@aws-sdk/client-sts@3.609.0): + resolution: {integrity: sha512-h+PFCWfZ0Q3Dx84SppET/TFpcQHmxFW8/oV9ArEvMilw4EBN+IlxgbL0CnHwjHW64szcmrM0mbebjEfHf4FXmw==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sts': ^3.637.0 + dependencies: + '@aws-sdk/client-sts': 3.609.0 + '@aws-sdk/credential-provider-env': 3.620.1 + '@aws-sdk/credential-provider-http': 3.635.0 + '@aws-sdk/credential-provider-process': 3.620.1 + '@aws-sdk/credential-provider-sso': 3.637.0(@aws-sdk/client-sso-oidc@3.637.0) + '@aws-sdk/credential-provider-web-identity': 3.621.0(@aws-sdk/client-sts@3.609.0) + '@aws-sdk/types': 3.609.0 + '@smithy/credential-provider-imds': 3.2.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + dev: false + + /@aws-sdk/credential-provider-ini@3.637.0(@aws-sdk/client-sso-oidc@3.637.0)(@aws-sdk/client-sts@3.637.0): + resolution: {integrity: sha512-h+PFCWfZ0Q3Dx84SppET/TFpcQHmxFW8/oV9ArEvMilw4EBN+IlxgbL0CnHwjHW64szcmrM0mbebjEfHf4FXmw==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sts': ^3.637.0 + dependencies: + '@aws-sdk/client-sts': 3.637.0 + '@aws-sdk/credential-provider-env': 3.620.1 + '@aws-sdk/credential-provider-http': 3.635.0 + '@aws-sdk/credential-provider-process': 3.620.1 + '@aws-sdk/credential-provider-sso': 3.637.0(@aws-sdk/client-sso-oidc@3.637.0) + '@aws-sdk/credential-provider-web-identity': 3.621.0(@aws-sdk/client-sts@3.637.0) + '@aws-sdk/types': 3.609.0 + '@smithy/credential-provider-imds': 3.2.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + dev: false + /@aws-sdk/credential-provider-node@3.600.0(@aws-sdk/client-sso-oidc@3.600.0)(@aws-sdk/client-sts@3.600.0): resolution: {integrity: sha512-1pC7MPMYD45J7yFjA90SxpR0yaSvy+yZiq23aXhAPZLYgJBAxHLu0s0mDCk/piWGPh8+UGur5K0bVdx4B1D5hw==} engines: {node: '>=16.0.0'} @@ -2821,6 +3183,72 @@ packages: - aws-crt dev: false + /@aws-sdk/credential-provider-node@3.609.0(@aws-sdk/client-sso-oidc@3.637.0)(@aws-sdk/client-sts@3.609.0): + resolution: {integrity: sha512-4J8/JRuqfxJDGD9jTHVCBxCvYt7/Vgj2Stlhj930mrjFPO/yRw8ilAAZxBWe0JHPX3QwepCmh4ErZe53F5ysxQ==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-sdk/credential-provider-env': 3.609.0 + '@aws-sdk/credential-provider-http': 3.609.0 + '@aws-sdk/credential-provider-ini': 3.609.0(@aws-sdk/client-sso-oidc@3.637.0)(@aws-sdk/client-sts@3.609.0) + '@aws-sdk/credential-provider-process': 3.609.0 + '@aws-sdk/credential-provider-sso': 3.609.0(@aws-sdk/client-sso-oidc@3.637.0) + '@aws-sdk/credential-provider-web-identity': 3.609.0(@aws-sdk/client-sts@3.609.0) + '@aws-sdk/types': 3.609.0 + '@smithy/credential-provider-imds': 3.1.3 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - '@aws-sdk/client-sts' + - aws-crt + dev: false + + /@aws-sdk/credential-provider-node@3.637.0(@aws-sdk/client-sso-oidc@3.637.0)(@aws-sdk/client-sts@3.609.0): + resolution: {integrity: sha512-yoEhoxJJfs7sPVQ6Is939BDQJZpZCoUgKr/ySse4YKOZ24t4VqgHA6+wV7rYh+7IW24Rd91UTvEzSuHYTlxlNA==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-sdk/credential-provider-env': 3.620.1 + '@aws-sdk/credential-provider-http': 3.635.0 + '@aws-sdk/credential-provider-ini': 3.637.0(@aws-sdk/client-sso-oidc@3.637.0)(@aws-sdk/client-sts@3.609.0) + '@aws-sdk/credential-provider-process': 3.620.1 + '@aws-sdk/credential-provider-sso': 3.637.0(@aws-sdk/client-sso-oidc@3.637.0) + '@aws-sdk/credential-provider-web-identity': 3.621.0(@aws-sdk/client-sts@3.609.0) + '@aws-sdk/types': 3.609.0 + '@smithy/credential-provider-imds': 3.2.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - '@aws-sdk/client-sts' + - aws-crt + dev: false + + /@aws-sdk/credential-provider-node@3.637.0(@aws-sdk/client-sso-oidc@3.637.0)(@aws-sdk/client-sts@3.637.0): + resolution: {integrity: sha512-yoEhoxJJfs7sPVQ6Is939BDQJZpZCoUgKr/ySse4YKOZ24t4VqgHA6+wV7rYh+7IW24Rd91UTvEzSuHYTlxlNA==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-sdk/credential-provider-env': 3.620.1 + '@aws-sdk/credential-provider-http': 3.635.0 + '@aws-sdk/credential-provider-ini': 3.637.0(@aws-sdk/client-sso-oidc@3.637.0)(@aws-sdk/client-sts@3.637.0) + '@aws-sdk/credential-provider-process': 3.620.1 + '@aws-sdk/credential-provider-sso': 3.637.0(@aws-sdk/client-sso-oidc@3.637.0) + '@aws-sdk/credential-provider-web-identity': 3.621.0(@aws-sdk/client-sts@3.637.0) + '@aws-sdk/types': 3.609.0 + '@smithy/credential-provider-imds': 3.2.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - '@aws-sdk/client-sts' + - aws-crt + dev: false + /@aws-sdk/credential-provider-process@3.598.0: resolution: {integrity: sha512-rM707XbLW8huMk722AgjVyxu2tMZee++fNA8TJVNgs1Ma02Wx6bBrfIvlyK0rCcIRb0WdQYP6fe3Xhiu4e8IBA==} engines: {node: '>=16.0.0'} @@ -2843,6 +3271,17 @@ packages: tslib: 2.6.3 dev: false + /@aws-sdk/credential-provider-process@3.620.1: + resolution: {integrity: sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-sdk/types': 3.609.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + dev: false + /@aws-sdk/credential-provider-sso@3.598.0(@aws-sdk/client-sso-oidc@3.600.0): resolution: {integrity: sha512-5InwUmrAuqQdOOgxTccRayMMkSmekdLk6s+az9tmikq0QFAHUCtofI+/fllMXSR9iL6JbGYi1940+EUmS4pHJA==} engines: {node: '>=16.0.0'} @@ -2867,7 +3306,39 @@ packages: '@aws-sdk/token-providers': 3.609.0(@aws-sdk/client-sso-oidc@3.609.0) '@aws-sdk/types': 3.609.0 '@smithy/property-provider': 3.1.3 - '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + dev: false + + /@aws-sdk/credential-provider-sso@3.609.0(@aws-sdk/client-sso-oidc@3.637.0): + resolution: {integrity: sha512-oQPGDKMMIxjvTcm86g07RPYeC7mCNk+29dPpY15ZAPRpAF7F0tircsC3wT9fHzNaKShEyK5LuI5Kg/uxsdy+Iw==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-sdk/client-sso': 3.609.0 + '@aws-sdk/token-providers': 3.609.0(@aws-sdk/client-sso-oidc@3.637.0) + '@aws-sdk/types': 3.609.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + dev: false + + /@aws-sdk/credential-provider-sso@3.637.0(@aws-sdk/client-sso-oidc@3.637.0): + resolution: {integrity: sha512-Mvz+h+e62/tl+dVikLafhv+qkZJ9RUb8l2YN/LeKMWkxQylPT83CPk9aimVhCV89zth1zpREArl97+3xsfgQvA==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-sdk/client-sso': 3.637.0 + '@aws-sdk/token-providers': 3.614.0(@aws-sdk/client-sso-oidc@3.637.0) + '@aws-sdk/types': 3.609.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: @@ -2901,7 +3372,33 @@ packages: tslib: 2.6.3 dev: false - /@aws-sdk/credential-providers@3.609.0(@aws-sdk/client-sso-oidc@3.609.0): + /@aws-sdk/credential-provider-web-identity@3.621.0(@aws-sdk/client-sts@3.609.0): + resolution: {integrity: sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sts': ^3.621.0 + dependencies: + '@aws-sdk/client-sts': 3.609.0 + '@aws-sdk/types': 3.609.0 + '@smithy/property-provider': 3.1.3 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + dev: false + + /@aws-sdk/credential-provider-web-identity@3.621.0(@aws-sdk/client-sts@3.637.0): + resolution: {integrity: sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sts': ^3.621.0 + dependencies: + '@aws-sdk/client-sts': 3.637.0 + '@aws-sdk/types': 3.609.0 + '@smithy/property-provider': 3.1.3 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + dev: false + + /@aws-sdk/credential-providers@3.609.0(@aws-sdk/client-sso-oidc@3.637.0): resolution: {integrity: sha512-bJKMY4QwRVderh8R2s9kukoZhuNZew/xzwPa9DRRFVOIsznsS0faAdmAAFrKb8e06YyQq6DiZP0BfFyVHAXE2A==} engines: {node: '>=16.0.0'} dependencies: @@ -2911,10 +3408,10 @@ packages: '@aws-sdk/credential-provider-cognito-identity': 3.609.0 '@aws-sdk/credential-provider-env': 3.609.0 '@aws-sdk/credential-provider-http': 3.609.0 - '@aws-sdk/credential-provider-ini': 3.609.0(@aws-sdk/client-sso-oidc@3.609.0)(@aws-sdk/client-sts@3.609.0) - '@aws-sdk/credential-provider-node': 3.609.0(@aws-sdk/client-sso-oidc@3.609.0)(@aws-sdk/client-sts@3.609.0) + '@aws-sdk/credential-provider-ini': 3.609.0(@aws-sdk/client-sso-oidc@3.637.0)(@aws-sdk/client-sts@3.609.0) + '@aws-sdk/credential-provider-node': 3.609.0(@aws-sdk/client-sso-oidc@3.637.0)(@aws-sdk/client-sts@3.609.0) '@aws-sdk/credential-provider-process': 3.609.0 - '@aws-sdk/credential-provider-sso': 3.609.0(@aws-sdk/client-sso-oidc@3.609.0) + '@aws-sdk/credential-provider-sso': 3.609.0(@aws-sdk/client-sso-oidc@3.637.0) '@aws-sdk/credential-provider-web-identity': 3.609.0(@aws-sdk/client-sts@3.609.0) '@aws-sdk/types': 3.609.0 '@smithy/credential-provider-imds': 3.1.3 @@ -2926,6 +3423,14 @@ packages: - aws-crt dev: false + /@aws-sdk/endpoint-cache@3.572.0: + resolution: {integrity: sha512-CzuRWMj/xtN9p9eP915nlPmlyniTzke732Ow/M60++gGgB3W+RtZyFftw3TEx+NzNhd1tH54dEcGiWdiNaBz3Q==} + engines: {node: '>=16.0.0'} + dependencies: + mnemonist: 0.38.3 + tslib: 2.6.3 + dev: false + /@aws-sdk/middleware-bucket-endpoint@3.598.0: resolution: {integrity: sha512-PM7BcFfGUSkmkT6+LU9TyJiB4S8yI7dfuKQDwK5ZR3P7MKaK4Uj4yyDiv0oe5xvkF6+O2+rShj+eh8YuWkOZ/Q==} engines: {node: '>=16.0.0'} @@ -2939,6 +3444,18 @@ packages: tslib: 2.6.3 dev: false + /@aws-sdk/middleware-endpoint-discovery@3.620.0: + resolution: {integrity: sha512-T6kuydHBF4BPP5CVH53Fze7c2b9rqxWP88XrGtmNMXXdY4sXur1v/itGdS2l3gqRjxKo0LsmjmuQm9zL4vGneQ==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-sdk/endpoint-cache': 3.572.0 + '@aws-sdk/types': 3.609.0 + '@smithy/node-config-provider': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + dev: false + /@aws-sdk/middleware-expect-continue@3.598.0: resolution: {integrity: sha512-ZuHW18kaeHR8TQyhEOYMr8VwiIh0bMvF7J1OTqXHxDteQIavJWA3CbfZ9sgS4XGtrBZDyHJhjZKeCfLhN2rq3w==} engines: {node: '>=16.0.0'} @@ -2983,6 +3500,16 @@ packages: tslib: 2.6.3 dev: false + /@aws-sdk/middleware-host-header@3.620.0: + resolution: {integrity: sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-sdk/types': 3.609.0 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + dev: false + /@aws-sdk/middleware-location-constraint@3.598.0: resolution: {integrity: sha512-8oybQxN3F1ISOMULk7JKJz5DuAm5hCUcxMW9noWShbxTJuStNvuHf/WLUzXrf8oSITyYzIHPtf8VPlKR7I3orQ==} engines: {node: '>=16.0.0'} @@ -3030,6 +3557,16 @@ packages: tslib: 2.6.3 dev: false + /@aws-sdk/middleware-recursion-detection@3.620.0: + resolution: {integrity: sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-sdk/types': 3.609.0 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + dev: false + /@aws-sdk/middleware-sdk-s3@3.598.0: resolution: {integrity: sha512-5AGtLAh9wyK6ANPYfaKTqJY1IFJyePIxsEbxa7zS6REheAqyVmgJFaGu3oQ5XlxfGr5Uq59tFTRkyx26G1HkHA==} engines: {node: '>=16.0.0'} @@ -3101,6 +3638,17 @@ packages: tslib: 2.6.3 dev: false + /@aws-sdk/middleware-user-agent@3.637.0: + resolution: {integrity: sha512-EYo0NE9/da/OY8STDsK2LvM4kNa79DBsf4YVtaG4P5pZ615IeFsD8xOHZeuJmUrSMlVQ8ywPRX7WMucUybsKug==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-sdk/types': 3.609.0 + '@aws-sdk/util-endpoints': 3.637.0 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + dev: false + /@aws-sdk/region-config-resolver@3.598.0: resolution: {integrity: sha512-oYXhmTokSav4ytmWleCr3rs/1nyvZW/S0tdi6X7u+dLNL5Jee+uMxWGzgOrWK6wrQOzucLVjS4E/wA11Kv2GTw==} engines: {node: '>=16.0.0'} @@ -3125,6 +3673,18 @@ packages: tslib: 2.6.3 dev: false + /@aws-sdk/region-config-resolver@3.614.0: + resolution: {integrity: sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-sdk/types': 3.609.0 + '@smithy/node-config-provider': 3.1.4 + '@smithy/types': 3.3.0 + '@smithy/util-config-provider': 3.0.0 + '@smithy/util-middleware': 3.0.3 + tslib: 2.6.3 + dev: false + /@aws-sdk/signature-v4-multi-region@3.598.0: resolution: {integrity: sha512-1r/EyTrO1gSa1FirnR8V7mabr7gk+l+HkyTI0fcTSr8ucB7gmYyW6WjkY8JCz13VYHFK62usCEDS7yoJoJOzTA==} engines: {node: '>=16.0.0'} @@ -3165,6 +3725,34 @@ packages: tslib: 2.6.3 dev: false + /@aws-sdk/token-providers@3.609.0(@aws-sdk/client-sso-oidc@3.637.0): + resolution: {integrity: sha512-WvhW/7XSf+H7YmtiIigQxfDVZVZI7mbKikQ09YpzN7FeN3TmYib1+0tB+EE9TbICkwssjiFc71FEBEh4K9grKQ==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sso-oidc': ^3.609.0 + dependencies: + '@aws-sdk/client-sso-oidc': 3.637.0(@aws-sdk/client-sts@3.609.0) + '@aws-sdk/types': 3.609.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + dev: false + + /@aws-sdk/token-providers@3.614.0(@aws-sdk/client-sso-oidc@3.637.0): + resolution: {integrity: sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sso-oidc': ^3.614.0 + dependencies: + '@aws-sdk/client-sso-oidc': 3.637.0(@aws-sdk/client-sts@3.609.0) + '@aws-sdk/types': 3.609.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + dev: false + /@aws-sdk/types@3.598.0: resolution: {integrity: sha512-742uRl6z7u0LFmZwDrFP6r1wlZcgVPw+/TilluDJmCAR8BgRw3IR+743kUXKBGd8QZDRW2n6v/PYsi/AWCDDMQ==} engines: {node: '>=16.0.0'} @@ -3188,6 +3776,16 @@ packages: tslib: 2.6.3 dev: false + /@aws-sdk/util-dynamodb@3.637.0(@aws-sdk/client-dynamodb@3.637.0): + resolution: {integrity: sha512-C2q8HcGRiahtf46Mhaqydh1gofeksj7m74PJXHYKW+pKBMLPlpou1+w2o5QSpVEp0dSBtKw30eRVQzxhqg/ACA==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-dynamodb': ^3.637.0 + dependencies: + '@aws-sdk/client-dynamodb': 3.637.0 + tslib: 2.6.3 + dev: false + /@aws-sdk/util-endpoints@3.598.0: resolution: {integrity: sha512-Qo9UoiVVZxcOEdiOMZg3xb1mzkTxrhd4qSlg5QQrfWPJVx/QOg+Iy0NtGxPtHtVZNHZxohYwDwV/tfsnDSE2gQ==} engines: {node: '>=16.0.0'} @@ -3208,6 +3806,16 @@ packages: tslib: 2.6.3 dev: false + /@aws-sdk/util-endpoints@3.637.0: + resolution: {integrity: sha512-pAqOKUHeVWHEXXDIp/qoMk/6jyxIb6GGjnK1/f8dKHtKIEs4tKsnnL563gceEvdad53OPXIt86uoevCcCzmBnw==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-sdk/types': 3.609.0 + '@smithy/types': 3.3.0 + '@smithy/util-endpoints': 2.0.5 + tslib: 2.6.3 + dev: false + /@aws-sdk/util-format-url@3.609.0: resolution: {integrity: sha512-fuk29BI/oLQlJ7pfm6iJ4gkEpHdavffAALZwXh9eaY1vQ0ip0aKfRTiNudPoJjyyahnz5yJ1HkmlcDitlzsOrQ==} engines: {node: '>=16.0.0'} @@ -3273,6 +3881,21 @@ packages: tslib: 2.6.3 dev: false + /@aws-sdk/util-user-agent-node@3.614.0: + resolution: {integrity: sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==} + engines: {node: '>=16.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + dependencies: + '@aws-sdk/types': 3.609.0 + '@smithy/node-config-provider': 3.1.4 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + dev: false + /@aws-sdk/util-utf8-browser@3.259.0: resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} dependencies: @@ -4119,6 +4742,17 @@ packages: tslib: 2.6.3 dev: false + /@smithy/config-resolver@3.0.5: + resolution: {integrity: sha512-SkW5LxfkSI1bUC74OtfBbdz+grQXYiPYolyu8VfpLIjEoN/sHVBlLeGXMQ1vX4ejkgfv6sxVbQJ32yF2cl1veA==} + engines: {node: '>=16.0.0'} + dependencies: + '@smithy/node-config-provider': 3.1.4 + '@smithy/types': 3.3.0 + '@smithy/util-config-provider': 3.0.0 + '@smithy/util-middleware': 3.0.3 + tslib: 2.6.3 + dev: false + /@smithy/core@2.2.4: resolution: {integrity: sha512-qdY3LpMOUyLM/gfjjMQZui+UTNS7kBRDWlvyIhVOql5dn2J3isk9qUTBtQ1CbDH8MTugHis1zu3h4rH+Qmmh4g==} engines: {node: '>=16.0.0'} @@ -4133,6 +4767,22 @@ packages: tslib: 2.6.3 dev: false + /@smithy/core@2.4.0: + resolution: {integrity: sha512-cHXq+FneIF/KJbt4q4pjN186+Jf4ZB0ZOqEaZMBhT79srEyGDDBV31NqBRBjazz8ppQ1bJbDJMY9ba5wKFV36w==} + engines: {node: '>=16.0.0'} + dependencies: + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 + '@smithy/middleware-serde': 3.0.3 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.3 + dev: false + /@smithy/credential-provider-imds@3.1.3: resolution: {integrity: sha512-U1Yrv6hx/mRK6k8AncuI6jLUx9rn0VVSd9NPEX6pyYFBfkSkChOc/n4zUb8alHUVg83TbI4OdZVo1X0Zfj3ijA==} engines: {node: '>=16.0.0'} @@ -4144,6 +4794,17 @@ packages: tslib: 2.6.3 dev: false + /@smithy/credential-provider-imds@3.2.0: + resolution: {integrity: sha512-0SCIzgd8LYZ9EJxUjLXBmEKSZR/P/w6l7Rz/pab9culE/RWuqelAKGJvn5qUOl8BgX8Yj5HWM50A5hiB/RzsgA==} + engines: {node: '>=16.0.0'} + dependencies: + '@smithy/node-config-provider': 3.1.4 + '@smithy/property-provider': 3.1.3 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 + tslib: 2.6.3 + dev: false + /@smithy/eventstream-codec@3.1.2: resolution: {integrity: sha512-0mBcu49JWt4MXhrhRAlxASNy0IjDRFU+aWNDRal9OtUJvJNiwDuyKMUONSOjLjSCeGwZaE0wOErdqULer8r7yw==} dependencies: @@ -4198,6 +4859,16 @@ packages: tslib: 2.6.3 dev: false + /@smithy/fetch-http-handler@3.2.4: + resolution: {integrity: sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==} + dependencies: + '@smithy/protocol-http': 4.1.0 + '@smithy/querystring-builder': 3.0.3 + '@smithy/types': 3.3.0 + '@smithy/util-base64': 3.0.0 + tslib: 2.6.3 + dev: false + /@smithy/hash-blob-browser@3.1.2: resolution: {integrity: sha512-hAbfqN2UbISltakCC2TP0kx4LqXBttEv2MqSPE98gVuDFMf05lU+TpC41QtqGP3Ff5A3GwZMPfKnEy0VmEUpmg==} dependencies: @@ -4264,6 +4935,15 @@ packages: tslib: 2.6.3 dev: false + /@smithy/middleware-content-length@3.0.5: + resolution: {integrity: sha512-ILEzC2eyxx6ncej3zZSwMpB5RJ0zuqH7eMptxC4KN3f+v9bqT8ohssKbhNR78k/2tWW+KS5Spw+tbPF4Ejyqvw==} + engines: {node: '>=16.0.0'} + dependencies: + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + dev: false + /@smithy/middleware-endpoint@3.0.4: resolution: {integrity: sha512-whUJMEPwl3ANIbXjBXZVdJNgfV2ZU8ayln7xUM47rXL2txuenI7jQ/VFFwCzy5lCmXScjp6zYtptW5Evud8e9g==} engines: {node: '>=16.0.0'} @@ -4277,6 +4957,34 @@ packages: tslib: 2.6.3 dev: false + /@smithy/middleware-endpoint@3.1.0: + resolution: {integrity: sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==} + engines: {node: '>=16.0.0'} + dependencies: + '@smithy/middleware-serde': 3.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 + '@smithy/util-middleware': 3.0.3 + tslib: 2.6.3 + dev: false + + /@smithy/middleware-retry@3.0.15: + resolution: {integrity: sha512-iTMedvNt1ApdvkaoE8aSDuwaoc+BhvHqttbA/FO4Ty+y/S5hW6Ci/CTScG7vam4RYJWZxdTElc3MEfHRVH6cgQ==} + engines: {node: '>=16.0.0'} + dependencies: + '@smithy/node-config-provider': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/service-error-classification': 3.0.3 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 + tslib: 2.6.3 + uuid: 9.0.1 + dev: false + /@smithy/middleware-retry@3.0.7: resolution: {integrity: sha512-f5q7Y09G+2h5ivkSx5CHvlAT4qRR3jBFEsfXyQ9nFNiWQlr8c48blnu5cmbTQ+p1xmIO14UXzKoF8d7Tm0Gsjw==} engines: {node: '>=16.0.0'} @@ -4318,6 +5026,16 @@ packages: tslib: 2.6.3 dev: false + /@smithy/node-config-provider@3.1.4: + resolution: {integrity: sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ==} + engines: {node: '>=16.0.0'} + dependencies: + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + dev: false + /@smithy/node-http-handler@3.1.1: resolution: {integrity: sha512-L71NLyPeP450r2J/mfu1jMc//Z1YnqJt2eSNw7uhiItaONnBLDA68J5jgxq8+MBDsYnFwNAIc7dBG1ImiWBiwg==} engines: {node: '>=16.0.0'} @@ -4329,6 +5047,17 @@ packages: tslib: 2.6.3 dev: false + /@smithy/node-http-handler@3.1.4: + resolution: {integrity: sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==} + engines: {node: '>=16.0.0'} + dependencies: + '@smithy/abort-controller': 3.1.1 + '@smithy/protocol-http': 4.1.0 + '@smithy/querystring-builder': 3.0.3 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + dev: false + /@smithy/property-provider@3.1.3: resolution: {integrity: sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==} engines: {node: '>=16.0.0'} @@ -4345,6 +5074,14 @@ packages: tslib: 2.6.3 dev: false + /@smithy/protocol-http@4.1.0: + resolution: {integrity: sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==} + engines: {node: '>=16.0.0'} + dependencies: + '@smithy/types': 3.3.0 + tslib: 2.6.3 + dev: false + /@smithy/querystring-builder@3.0.3: resolution: {integrity: sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==} engines: {node: '>=16.0.0'} @@ -4377,6 +5114,14 @@ packages: tslib: 2.6.3 dev: false + /@smithy/shared-ini-file-loader@3.1.4: + resolution: {integrity: sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ==} + engines: {node: '>=16.0.0'} + dependencies: + '@smithy/types': 3.3.0 + tslib: 2.6.3 + dev: false + /@smithy/signature-v4@2.3.0: resolution: {integrity: sha512-ui/NlpILU+6HAQBfJX8BBsDXuKSNrjTSuOYArRblcrErwKFutjrCNb/OExfVRyj9+26F9J+ZmfWT+fKWuDrH3Q==} engines: {node: '>=14.0.0'} @@ -4403,6 +5148,20 @@ packages: tslib: 2.6.3 dev: false + /@smithy/signature-v4@4.1.0: + resolution: {integrity: sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==} + engines: {node: '>=16.0.0'} + dependencies: + '@smithy/is-array-buffer': 3.0.0 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 + '@smithy/util-hex-encoding': 3.0.0 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-uri-escape': 3.0.0 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.3 + dev: false + /@smithy/smithy-client@3.1.5: resolution: {integrity: sha512-x9bL9Mx2CT2P1OiUlHM+ZNpbVU6TgT32f9CmTRzqIHA7M4vYrROCWEoC3o4xHNJASoGd4Opos3cXYPgh+/m4Ww==} engines: {node: '>=16.0.0'} @@ -4415,6 +5174,18 @@ packages: tslib: 2.6.3 dev: false + /@smithy/smithy-client@3.2.0: + resolution: {integrity: sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==} + engines: {node: '>=16.0.0'} + dependencies: + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-stack': 3.0.3 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 + '@smithy/util-stream': 3.1.3 + tslib: 2.6.3 + dev: false + /@smithy/types@2.12.0: resolution: {integrity: sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==} engines: {node: '>=14.0.0'} @@ -4482,6 +5253,17 @@ packages: tslib: 2.6.3 dev: false + /@smithy/util-defaults-mode-browser@3.0.15: + resolution: {integrity: sha512-FZ4Psa3vjp8kOXcd3HJOiDPBCWtiilLl57r0cnNtq/Ga9RSDrM5ERL6xt+tO43+2af6Pn5Yp92x2n5vPuduNfg==} + engines: {node: '>= 10.0.0'} + dependencies: + '@smithy/property-provider': 3.1.3 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + bowser: 2.11.0 + tslib: 2.6.3 + dev: false + /@smithy/util-defaults-mode-browser@3.0.7: resolution: {integrity: sha512-Q2txLyvQyGfmjsaDbVV7Sg8psefpFcrnlGapDzXGFRPFKRBeEg6OvFK8FljqjeHSaCZ6/UuzQExUPqBR/2qlDA==} engines: {node: '>= 10.0.0'} @@ -4493,6 +5275,19 @@ packages: tslib: 2.6.3 dev: false + /@smithy/util-defaults-mode-node@3.0.15: + resolution: {integrity: sha512-KSyAAx2q6d0t6f/S4XB2+3+6aQacm3aLMhs9aLMqn18uYGUepbdssfogW5JQZpc6lXNBnp0tEnR5e9CEKmEd7A==} + engines: {node: '>= 10.0.0'} + dependencies: + '@smithy/config-resolver': 3.0.5 + '@smithy/credential-provider-imds': 3.2.0 + '@smithy/node-config-provider': 3.1.4 + '@smithy/property-provider': 3.1.3 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + dev: false + /@smithy/util-defaults-mode-node@3.0.7: resolution: {integrity: sha512-F4Qcj1fG6MGi2BSWCslfsMSwllws/WzYONBGtLybyY+halAcXdWhcew+mej8M5SKd5hqPYp4f7b+ABQEaeytgg==} engines: {node: '>= 10.0.0'} @@ -4515,6 +5310,15 @@ packages: tslib: 2.6.3 dev: false + /@smithy/util-endpoints@2.0.5: + resolution: {integrity: sha512-ReQP0BWihIE68OAblC/WQmDD40Gx+QY1Ez8mTdFMXpmjfxSyz2fVQu3A4zXRfQU9sZXtewk3GmhfOHswvX+eNg==} + engines: {node: '>=16.0.0'} + dependencies: + '@smithy/node-config-provider': 3.1.4 + '@smithy/types': 3.3.0 + tslib: 2.6.3 + dev: false + /@smithy/util-hex-encoding@2.2.0: resolution: {integrity: sha512-7iKXR+/4TpLK194pVjKiasIyqMtTYJsgKgM242Y9uzt5dhHnUDvMNb+3xIhRJ9QhvqGii/5cRUt4fJn3dtXNHQ==} engines: {node: '>=14.0.0'} @@ -4568,6 +5372,20 @@ packages: tslib: 2.6.3 dev: false + /@smithy/util-stream@3.1.3: + resolution: {integrity: sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==} + engines: {node: '>=16.0.0'} + dependencies: + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/types': 3.3.0 + '@smithy/util-base64': 3.0.0 + '@smithy/util-buffer-from': 3.0.0 + '@smithy/util-hex-encoding': 3.0.0 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.3 + dev: false + /@smithy/util-uri-escape@2.2.0: resolution: {integrity: sha512-jtmJMyt1xMD/d8OtbVJ2gFZOSKc+ueYJZPW20ULW1GOp/q/YIM0wNh+u8ZFao9UaIGz4WoPW8hC64qlWLIfoDA==} engines: {node: '>=14.0.0'} @@ -5314,13 +6132,13 @@ packages: possible-typed-array-names: 1.0.0 dev: true - /aws-msk-iam-sasl-signer-js@1.0.0(@aws-sdk/client-sso-oidc@3.609.0): + /aws-msk-iam-sasl-signer-js@1.0.0(@aws-sdk/client-sso-oidc@3.637.0): resolution: {integrity: sha512-L0Jk0k2XNHMSGipJ8rRdTq51KrH/gwrfZ39iKY9BWHGOAv7EygsG4qJC7lIRsbu5/ZHB886Z3WsOsFxqR2R4XQ==} engines: {node: '>=14.x'} dependencies: '@aws-crypto/sha256-js': 4.0.0 '@aws-sdk/client-sts': 3.609.0 - '@aws-sdk/credential-providers': 3.609.0(@aws-sdk/client-sso-oidc@3.609.0) + '@aws-sdk/credential-providers': 3.609.0(@aws-sdk/client-sso-oidc@3.637.0) '@aws-sdk/util-format-url': 3.609.0 '@smithy/signature-v4': 2.3.0 '@types/buffers': 0.1.31 @@ -6686,6 +7504,13 @@ packages: strnum: 1.0.5 dev: false + /fast-xml-parser@4.4.1: + resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==} + hasBin: true + dependencies: + strnum: 1.0.5 + dev: false + /fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} dependencies: @@ -7812,6 +8637,12 @@ packages: ufo: 1.5.3 dev: true + /mnemonist@0.38.3: + resolution: {integrity: sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==} + dependencies: + obliterator: 1.6.1 + dev: false + /mongodb-connection-string-url@3.0.1: resolution: {integrity: sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==} dependencies: @@ -7995,6 +8826,10 @@ packages: es-object-atoms: 1.0.0 dev: true + /obliterator@1.6.1: + resolution: {integrity: sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==} + dev: false + /on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} From bdf51f27e9267f6b5cc3746682816c726ecaac1e Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 27 Aug 2024 16:40:13 +0200 Subject: [PATCH 011/241] Work in progress --- packages/catalog-platformstate-writer/.env | 3 +- .../catalog-platformstate-writer/src/utils.ts | 8 +- ...logPlatformstateWriter.integration.test.ts | 309 +++++++++++------- .../test/utils.ts | 10 +- .../commons-test/src/containerTestUtils.ts | 15 + .../src/setupTestContainersVitestGlobal.ts | 20 ++ .../tokenGenerationReadmodelDbConfig.ts | 8 +- 7 files changed, 239 insertions(+), 134 deletions(-) diff --git a/packages/catalog-platformstate-writer/.env b/packages/catalog-platformstate-writer/.env index 1f9a570471..6db18881a4 100644 --- a/packages/catalog-platformstate-writer/.env +++ b/packages/catalog-platformstate-writer/.env @@ -8,6 +8,7 @@ CATALOG_TOPIC="event-store.catalog.events" AWS_CONFIG_FILE=aws.config.local TOKEN_GENERATION_READMODEL_HOST="localhost" TOKEN_GENERATION_READMODEL_PORT=8085 -TOKEN_GENERATION_READMODEL_TABLE_NAME="PlatformStates" +TOKEN_GENERATION_READMODEL_TABLE_NAME_PLATFORM="PlatformStates" +TOKEN_GENERATION_READMODEL_TABLE_NAME_TOKEN_GENERATION="TokenGenerationStates" AWS_REGION="eu-central-1" diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index 8cdcf8d13b..d52303aa0d 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -34,7 +34,7 @@ export const writeCatalogEntry = async ( S: catalogEntry.descriptorAudience, }, }, - TableName: config.tokenGenerationReadModelTableName, + TableName: config.tokenGenerationReadModelTableNamePlatform, }; const command = new PutItemCommand(input); await dynamoDBClient.send(command); @@ -48,7 +48,7 @@ export const readCatalogEntry = async ( Key: { PK: { S: primaryKey }, }, - TableName: config.tokenGenerationReadModelTableName, + TableName: config.tokenGenerationReadModelTableNamePlatform, }; const command = new GetItemCommand(input); const data: GetItemCommandOutput = await dynamoDBClient.send(command); @@ -78,7 +78,7 @@ export const deleteCatalogEntry = async ( Key: { PK: { S: primaryKey }, }, - TableName: config.tokenGenerationReadModelTableName, + TableName: config.tokenGenerationReadModelTableNamePlatform, }; const command = new DeleteItemCommand(input); await dynamoDBClient.send(command); @@ -88,7 +88,7 @@ export const readAllItems = async ( dynamoDBClient: DynamoDBClient ): Promise => { const readInput: ScanInput = { - TableName: config.tokenGenerationReadModelTableName, + TableName: config.tokenGenerationReadModelTableNamePlatform, }; const commandQuery = new ScanCommand(readInput); const read: ScanCommandOutput = await dynamoDBClient.send(commandQuery); diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 5610501388..8b77bd2433 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -1,11 +1,5 @@ -import { describe, expect, it } from "vitest"; -import { - getMockValidRiskAnalysis, - writeInReadmodel, - toEServiceV1, - toDocumentV1, - toDescriptorV1, -} from "pagopa-interop-commons-test"; +import { format } from "util"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { AttributeId, ClonedEServiceAddedV1, @@ -38,7 +32,6 @@ import { EServiceDraftDescriptorDeletedV2, EServiceDraftDescriptorUpdatedV2, EServiceEventEnvelope, - EServiceReadModel, EServiceRiskAnalysisAddedV1, EServiceRiskAnalysisAddedV2, EServiceRiskAnalysisDeletedV1, @@ -47,22 +40,92 @@ import { EServiceWithDescriptorsDeletedV1, EserviceAttributes, MovedAttributesFromEserviceToDescriptorsV1, + PlatformStatesCatalogEntry, RiskAnalysis, descriptorState, eserviceMode, generateId, technology, toEServiceV2, - toReadModelEService, } from "pagopa-interop-models"; -import { format } from "date-fns"; +import * as dynamodb from "@aws-sdk/client-dynamodb"; +import { + toEServiceV1, + getMockValidRiskAnalysis, + toDocumentV1, + toDescriptorV1, +} from "pagopa-interop-commons-test/index.js"; +import { + readAllItems, + readCatalogEntry, + writeCatalogEntry, +} from "../src/utils.js"; import { handleMessageV1 } from "../src/consumerServiceV1.js"; import { handleMessageV2 } from "../src/consumerServiceV2.js"; -import { eservices } from "./utils.js"; + +import { config } from "./utils.js"; describe("database test", async () => { + const dynamoDBClient = new dynamodb.DynamoDB({ + credentials: { accessKeyId: "key", secretAccessKey: "secret" }, + region: "eu-central-1", + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + endpoint: `http://${config!.tokenGenerationReadModelDbHost}:${ + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + config!.tokenGenerationReadModelDbPort + }`, + }); + beforeAll(async () => { + const platformTableDefinition: dynamodb.CreateTableInput = { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + TableName: config!.tokenGenerationReadModelTableNamePlatform, + AttributeDefinitions: [{ AttributeName: "PK", AttributeType: "S" }], + KeySchema: [{ AttributeName: "PK", KeyType: "HASH" }], + BillingMode: "PAY_PER_REQUEST", + }; + await dynamoDBClient.createTable(platformTableDefinition); + + const tokenGenerationTableDefinition: dynamodb.CreateTableInput = { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + TableName: config!.tokenGenerationReadModelTableNameTokenGeneration, + AttributeDefinitions: [{ AttributeName: "PK", AttributeType: "S" }], + KeySchema: [{ AttributeName: "PK", KeyType: "HASH" }], + BillingMode: "PAY_PER_REQUEST", + }; + await dynamoDBClient.createTable(tokenGenerationTableDefinition); + + const tablesResult = await dynamoDBClient.listTables(); + console.log(tablesResult.TableNames); + }); + afterAll(async () => { + const tableToDelete1: dynamodb.DeleteTableInput = { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + TableName: config!.tokenGenerationReadModelTableNamePlatform, + }; + const tableToDelete2: dynamodb.DeleteTableInput = { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + TableName: config!.tokenGenerationReadModelTableNameTokenGeneration, + }; + await dynamoDBClient.deleteTable(tableToDelete1); + await dynamoDBClient.deleteTable(tableToDelete2); + }); describe("Events V1", async () => { const mockEService = getMockEService(); + it("sample", async () => { + const key = `ESERVICEDESCRIPTOR#${generateId()}#${generateId()}`; + + const catalogEntry: PlatformStatesCatalogEntry = { + PK: key, + state: "ACTIVE", + descriptorAudience: "pagopa.it", + }; + await writeCatalogEntry(catalogEntry, dynamoDBClient); + + const result = await readCatalogEntry(key, dynamoDBClient); + + // const resultFetchAll = await readAllItems(dynamoDBClient); + expect(result).toEqual(catalogEntry); + }); it("EServiceAdded", async () => { const payload: EServiceAddedV1 = { @@ -77,18 +140,18 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV1(message, eservices); + await handleMessageV1(message, dynamoDBClient); // TO DO expect(1).toBe(1); }); it("ClonedEServiceAdded", async () => { - await writeInReadmodel( - toReadModelEService(mockEService), - eservices, - 1 - ); + // await writeInReadmodel( + // toReadModelEService(mockEService), + // eservices, + // 1 + // ); const date = new Date(); const clonedEService: EService = { @@ -113,18 +176,18 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV1(message, eservices); + await handleMessageV1(message, dynamoDBClient); // TO DO expect(1).toBe(1); }); it("EServiceUpdated", async () => { - await writeInReadmodel( - toReadModelEService(mockEService), - eservices, - 1 - ); + // await writeInReadmodel( + // toReadModelEService(mockEService), + // eservices, + // 1 + // ); const updatedEService: EService = { ...mockEService, @@ -142,18 +205,18 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV1(message, eservices); + await handleMessageV1(message, dynamoDBClient); // TO DO expect(1).toBe(1); }); it("EServiceRiskAnalysisAdded", async () => { - await writeInReadmodel( - toReadModelEService(mockEService), - eservices, - 1 - ); + // await writeInReadmodel( + // toReadModelEService(mockEService), + // eservices, + // 1 + // ); const mockRiskAnalysis = getMockValidRiskAnalysis("PA"); const updatedEService: EService = { @@ -173,7 +236,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV1(message, eservices); + await handleMessageV1(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -197,12 +260,12 @@ describe("database test", async () => { state: descriptorState.draft, attributes, }; - const eservice: EService = { - ...mockEService, - attributes, - descriptors: [descriptor], - }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // const eservice: EService = { + // ...mockEService, + // attributes, + // descriptors: [descriptor], + // }; + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const updatedDescriptor = { ...descriptor, attributes, @@ -224,7 +287,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV1(message, eservices); + await handleMessageV1(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -239,7 +302,7 @@ describe("database test", async () => { ...mockEService, descriptors: [draftDescriptor], }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const updatedEService: EService = { ...eservice, @@ -258,7 +321,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV1(message, eservices); + await handleMessageV1(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -275,7 +338,7 @@ describe("database test", async () => { ...mockEService, descriptors: [draftDescriptor], }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const updatedDocument: Document = { ...document, @@ -298,14 +361,14 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV1(message, eservices); + await handleMessageV1(message, dynamoDBClient); // TO DO expect(1).toBe(1); }); it("EServiceDeleted", async () => { - await writeInReadmodel(toReadModelEService(mockEService), eservices, 1); + // await writeInReadmodel(toReadModelEService(mockEService), eservices, 1); const payload: EServiceDeletedV1 = { eserviceId: mockEService.id, @@ -319,7 +382,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV1(message, eservices); + await handleMessageV1(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -337,7 +400,7 @@ describe("database test", async () => { ...mockEService, descriptors: [draftDescriptor], }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const payload: EServiceDocumentAddedV1 = { eserviceId: eservice.id, @@ -355,7 +418,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV1(message, eservices); + await handleMessageV1(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -372,7 +435,7 @@ describe("database test", async () => { ...mockEService, descriptors: [draftDescriptor], }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const payload: EServiceDocumentAddedV1 = { eserviceId: eservice.id, @@ -390,7 +453,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV1(message, eservices); + await handleMessageV1(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -409,7 +472,7 @@ describe("database test", async () => { ...mockEService, descriptors: [draftDescriptor], }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const payload: EServiceDocumentDeletedV1 = { eserviceId: eservice.id, @@ -426,7 +489,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV1(message, eservices); + await handleMessageV1(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -443,7 +506,7 @@ describe("database test", async () => { ...mockEService, descriptors: [draftDescriptor], }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const payload: EServiceDocumentDeletedV1 = { eserviceId: eservice.id, @@ -460,7 +523,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV1(message, eservices); + await handleMessageV1(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -476,7 +539,7 @@ describe("database test", async () => { ...mockEService, descriptors: [], }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const payload: EServiceDescriptorAddedV1 = { eserviceId: eservice.id, @@ -491,7 +554,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV1(message, eservices); + await handleMessageV1(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -507,7 +570,7 @@ describe("database test", async () => { ...mockEService, descriptors: [draftDescriptor], }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const publishedDescriptor: Descriptor = { ...draftDescriptor, @@ -528,7 +591,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV1(message, eservices); + await handleMessageV1(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -536,12 +599,12 @@ describe("database test", async () => { it("EServiceRiskAnalysisDeleted", async () => { const riskAnalysis = getMockValidRiskAnalysis("PA"); - const eservice: EService = { - ...mockEService, - riskAnalysis: [riskAnalysis], - }; + // const eservice: EService = { + // ...mockEService, + // riskAnalysis: [riskAnalysis], + // }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const updatedEService: EService = { ...mockEService, @@ -560,7 +623,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV1(message, eservices); + await handleMessageV1(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -570,7 +633,7 @@ describe("database test", async () => { describe("Events V2", async () => { const mockEService = getMockEService(); it("EServiceDeleted", async () => { - await writeInReadmodel(toReadModelEService(mockEService), eservices, 1); + // await writeInReadmodel(toReadModelEService(mockEService), eservices, 1); const payload: EServiceDeletedV2 = { eserviceId: mockEService.id, @@ -584,7 +647,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV2(message, eservices); + await handleMessageV2(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -603,18 +666,18 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV2(message, eservices); + await handleMessageV2(message, dynamoDBClient); // TO DO expect(1).toBe(1); }); it("DraftEServiceUpdated", async () => { - await writeInReadmodel( - toReadModelEService(mockEService), - eservices, - 1 - ); + // await writeInReadmodel( + // toReadModelEService(mockEService), + // eservices, + // 1 + // ); const updatedEService: EService = { ...mockEService, @@ -632,7 +695,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV2(message, eservices); + await handleMessageV2(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -649,11 +712,11 @@ describe("database test", async () => { ...mockEService, descriptors: [sourceDescriptor], }; - await writeInReadmodel( - toReadModelEService(sourceEService), - eservices, - 1 - ); + // await writeInReadmodel( + // toReadModelEService(sourceEService), + // eservices, + // 1 + // ); const date = new Date(); const clonedEService: EService = { @@ -687,7 +750,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV2(message, eservices); + await handleMessageV2(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -702,7 +765,7 @@ describe("database test", async () => { ...mockEService, descriptors: [], }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const updatedEService: EService = { ...eservice, @@ -721,7 +784,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV2(message, eservices); + await handleMessageV2(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -736,7 +799,7 @@ describe("database test", async () => { ...mockEService, descriptors: [draftDescriptor], }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const updatedEService: EService = { ...eservice, @@ -755,7 +818,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV2(message, eservices); + await handleMessageV2(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -771,7 +834,7 @@ describe("database test", async () => { ...mockEService, descriptors: [draftDescriptor], }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const updatedDraftDescriptor: Descriptor = { ...draftDescriptor, @@ -794,7 +857,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV2(message, eservices); + await handleMessageV2(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -811,7 +874,7 @@ describe("database test", async () => { ...mockEService, descriptors: [publishedDescriptor], }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const updatedPublishedDescriptor: Descriptor = { ...publishedDescriptor, @@ -835,7 +898,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV2(message, eservices); + await handleMessageV2(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -853,7 +916,7 @@ describe("database test", async () => { ...mockEService, descriptors: [suspendedDescriptor], }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const publishedDescriptor: Descriptor = { ...suspendedDescriptor, @@ -878,7 +941,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV2(message, eservices); + await handleMessageV2(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -895,7 +958,7 @@ describe("database test", async () => { ...mockEService, descriptors: [publishedDescriptor], }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const archivedDescriptor: Descriptor = { ...publishedDescriptor, @@ -919,7 +982,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV2(message, eservices); + await handleMessageV2(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -935,7 +998,7 @@ describe("database test", async () => { ...mockEService, descriptors: [draftDescriptor], }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const publishedDescriptor: Descriptor = { ...draftDescriptor, @@ -959,7 +1022,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV2(message, eservices); + await handleMessageV2(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -976,7 +1039,7 @@ describe("database test", async () => { ...mockEService, descriptors: [publishedDescriptor], }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const suspendedDescriptor: Descriptor = { ...publishedDescriptor, @@ -1000,7 +1063,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV2(message, eservices); + await handleMessageV2(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -1017,7 +1080,7 @@ describe("database test", async () => { ...mockEService, descriptors: [draftDescriptor], }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const updatedEService: EService = { ...eservice, descriptors: [{ ...draftDescriptor, interface: descriptorInterface }], @@ -1036,7 +1099,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV2(message, eservices); + await handleMessageV2(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -1053,7 +1116,7 @@ describe("database test", async () => { ...mockEService, descriptors: [draftDescriptor], }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const updatedEService: EService = { ...eservice, @@ -1073,7 +1136,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV2(message, eservices); + await handleMessageV2(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -1090,7 +1153,7 @@ describe("database test", async () => { ...mockEService, descriptors: [draftDescriptor], }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const updatedInterface: Document = { ...descriptorInterface, @@ -1114,7 +1177,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV2(message, eservices); + await handleMessageV2(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -1131,7 +1194,7 @@ describe("database test", async () => { ...mockEService, descriptors: [draftDescriptor], }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const updatedDocument: Document = { ...document, @@ -1155,7 +1218,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV2(message, eservices); + await handleMessageV2(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -1172,7 +1235,7 @@ describe("database test", async () => { ...mockEService, descriptors: [draftDescriptor], }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const updatedEService: EService = { ...eservice, @@ -1195,7 +1258,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV2(message, eservices); + await handleMessageV2(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -1212,7 +1275,7 @@ describe("database test", async () => { ...mockEService, descriptors: [draftDescriptor], }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const updatedEService: EService = { ...eservice, @@ -1233,18 +1296,18 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV2(message, eservices); + await handleMessageV2(message, dynamoDBClient); // TO DO expect(1).toBe(1); }); it("EServiceRiskAnalysisAdded", async () => { - await writeInReadmodel( - toReadModelEService(mockEService), - eservices, - 1 - ); + // await writeInReadmodel( + // toReadModelEService(mockEService), + // eservices, + // 1 + // ); const mockRiskAnalysis = getMockValidRiskAnalysis("PA"); const updatedEService: EService = { @@ -1264,7 +1327,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV2(message, eservices); + await handleMessageV2(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -1276,11 +1339,11 @@ describe("database test", async () => { ...mockEService, riskAnalysis: [mockRiskAnalysis], }; - await writeInReadmodel( - toReadModelEService(eservice), - eservices, - 1 - ); + // await writeInReadmodel( + // toReadModelEService(eservice), + // eservices, + // 1 + // ); const updatedRiskAnalysis: RiskAnalysis = { ...mockRiskAnalysis, @@ -1312,7 +1375,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV2(message, eservices); + await handleMessageV2(message, dynamoDBClient); // TO DO expect(1).toBe(1); @@ -1320,12 +1383,12 @@ describe("database test", async () => { it("EServiceRiskAnalysisDeleted", async () => { const riskAnalysis = getMockValidRiskAnalysis("PA"); - const eservice: EService = { - ...mockEService, - riskAnalysis: [riskAnalysis], - }; + // const eservice: EService = { + // ...mockEService, + // riskAnalysis: [riskAnalysis], + // }; - await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const updatedEService: EService = { ...mockEService, @@ -1344,7 +1407,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - await handleMessageV2(message, eservices); + await handleMessageV2(message, dynamoDBClient); // TO DO expect(1).toBe(1); diff --git a/packages/catalog-platformstate-writer/test/utils.ts b/packages/catalog-platformstate-writer/test/utils.ts index 0fcfb8585d..1931401502 100644 --- a/packages/catalog-platformstate-writer/test/utils.ts +++ b/packages/catalog-platformstate-writer/test/utils.ts @@ -1,7 +1,9 @@ -// export const { cleanup, readModelRepository } = setupTestContainersVitest( -// inject("tokenGenerationReadModelConfig") -// ); +import { setupTestContainersVitest } from "pagopa-interop-commons-test/index.js"; +import { afterEach, inject } from "vitest"; -// afterEach(cleanup); +export const config = inject("tokenGenerationReadModelConfig"); +export const { cleanup } = setupTestContainersVitest(); + +afterEach(cleanup); // export const eservices = readModelRepository.eservices; diff --git a/packages/commons-test/src/containerTestUtils.ts b/packages/commons-test/src/containerTestUtils.ts index 9ffdfd9e96..b3a9233930 100644 --- a/packages/commons-test/src/containerTestUtils.ts +++ b/packages/commons-test/src/containerTestUtils.ts @@ -2,6 +2,7 @@ import { EventStoreConfig, ReadModelDbConfig, S3Config, + TokenGenerationReadModelDbConfig, } from "pagopa-interop-commons"; import { GenericContainer } from "testcontainers"; @@ -11,6 +12,9 @@ export const TEST_MONGO_DB_IMAGE = "mongo:4.0"; export const TEST_POSTGRES_DB_PORT = 5432; export const TEST_POSTGRES_DB_IMAGE = "postgres:14"; +export const TEST_DYNAMODB_PORT = 8000; +export const TEST_DYNAMODB_IMAGE = "amazon/dynamodb-local:latest"; + export const TEST_MINIO_PORT = 9000; export const TEST_MINIO_IMAGE = "quay.io/minio/minio:RELEASE.2024-02-06T21-36-22Z"; @@ -57,6 +61,17 @@ export const postgreSQLContainer = ( ]) .withExposedPorts(TEST_POSTGRES_DB_PORT); +/** + * Starts a DynamoDB container for testing purposes. + * + * @param config - The configuration for the DynamoDB container. + * @returns A promise that resolves to the started test container. + */ +export const dynamoDBContainer = (): GenericContainer => + new GenericContainer(TEST_DYNAMODB_IMAGE) + // .withCommand(["-jar DynamoDBLocal.jar -port 8000 -inMemory -sharedDb"]) + .withExposedPorts(TEST_DYNAMODB_PORT); + /** * Starts a MinIO container for testing purposes. * diff --git a/packages/commons-test/src/setupTestContainersVitestGlobal.ts b/packages/commons-test/src/setupTestContainersVitestGlobal.ts index 3d11ef75ee..ec21c65348 100644 --- a/packages/commons-test/src/setupTestContainersVitestGlobal.ts +++ b/packages/commons-test/src/setupTestContainersVitestGlobal.ts @@ -13,6 +13,7 @@ import { LoggerConfig, ReadModelDbConfig, S3Config, + TokenGenerationReadModelDbConfig, } from "pagopa-interop-commons"; import { TEST_MINIO_PORT, @@ -24,12 +25,15 @@ import { mailpitContainer, TEST_MAILPIT_SMTP_PORT, TEST_MAILPIT_HTTP_PORT, + dynamoDBContainer, + TEST_DYNAMODB_PORT, } from "./containerTestUtils.js"; import { EmailManagerConfigTest } from "./testConfig.js"; declare module "vitest" { export interface ProvidedContext { readModelConfig?: ReadModelDbConfig; + tokenGenerationReadModelConfig?: TokenGenerationReadModelDbConfig; eventStoreConfig?: EventStoreConfig; fileManagerConfig?: FileManagerConfig & LoggerConfig & S3Config; emailManagerConfig?: EmailManagerConfigTest; @@ -52,6 +56,8 @@ export function setupTestContainersVitestGlobal() { .and(LoggerConfig) .safeParse(process.env); const emailManagerConfig = EmailManagerConfigTest.safeParse(process.env); + const tokenGenerationReadModelConfig = + TokenGenerationReadModelDbConfig.safeParse(process.env); return async function ({ provide, @@ -60,6 +66,7 @@ export function setupTestContainersVitestGlobal() { let startedMongodbContainer: StartedTestContainer | undefined; let startedMinioContainer: StartedTestContainer | undefined; let startedMailpitContainer: StartedTestContainer | undefined; + let startedDynamoDbContainer: StartedTestContainer | undefined; // Setting up the EventStore PostgreSQL container if the config is provided if (eventStoreConfig.success) { @@ -124,11 +131,24 @@ export function setupTestContainersVitestGlobal() { provide("emailManagerConfig", emailManagerConfig.data); } + // Setting up the DynamoDB container if the config is provided + if (tokenGenerationReadModelConfig.success) { + startedDynamoDbContainer = await dynamoDBContainer().start(); + tokenGenerationReadModelConfig.data.tokenGenerationReadModelDbPort = + startedDynamoDbContainer.getMappedPort(TEST_DYNAMODB_PORT); + + provide( + "tokenGenerationReadModelConfig", + tokenGenerationReadModelConfig.data + ); + } + return async (): Promise => { await startedPostgreSqlContainer?.stop(); await startedMongodbContainer?.stop(); await startedMinioContainer?.stop(); await startedMailpitContainer?.stop(); + await startedDynamoDbContainer?.stop(); }; }; } diff --git a/packages/commons/src/config/tokenGenerationReadmodelDbConfig.ts b/packages/commons/src/config/tokenGenerationReadmodelDbConfig.ts index e95ed92942..6ad8699555 100644 --- a/packages/commons/src/config/tokenGenerationReadmodelDbConfig.ts +++ b/packages/commons/src/config/tokenGenerationReadmodelDbConfig.ts @@ -4,12 +4,16 @@ export const TokenGenerationReadModelDbConfig = z .object({ TOKEN_GENERATION_READMODEL_HOST: z.string(), TOKEN_GENERATION_READMODEL_PORT: z.coerce.number().min(1001), - TOKEN_GENERATION_READMODEL_TABLE_NAME: z.string(), + TOKEN_GENERATION_READMODEL_TABLE_NAME_PLATFORM: z.string(), + TOKEN_GENERATION_READMODEL_TABLE_NAME_TOKEN_GENERATION: z.string(), }) .transform((c) => ({ tokenGenerationReadModelDbHost: c.TOKEN_GENERATION_READMODEL_HOST, tokenGenerationReadModelDbPort: c.TOKEN_GENERATION_READMODEL_PORT, - tokenGenerationReadModelTableName: c.TOKEN_GENERATION_READMODEL_TABLE_NAME, + tokenGenerationReadModelTableNamePlatform: + c.TOKEN_GENERATION_READMODEL_TABLE_NAME_PLATFORM, + tokenGenerationReadModelTableNameTokenGeneration: + c.TOKEN_GENERATION_READMODEL_TABLE_NAME_TOKEN_GENERATION, })); export type TokenGenerationReadModelDbConfig = z.infer< From 28c8826478aac8c3bf691cca1f2eed935a9c12bc Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 27 Aug 2024 16:42:01 +0200 Subject: [PATCH 012/241] Edit commented line --- packages/commons-test/src/containerTestUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/commons-test/src/containerTestUtils.ts b/packages/commons-test/src/containerTestUtils.ts index b3a9233930..ce7b6a948a 100644 --- a/packages/commons-test/src/containerTestUtils.ts +++ b/packages/commons-test/src/containerTestUtils.ts @@ -69,7 +69,7 @@ export const postgreSQLContainer = ( */ export const dynamoDBContainer = (): GenericContainer => new GenericContainer(TEST_DYNAMODB_IMAGE) - // .withCommand(["-jar DynamoDBLocal.jar -port 8000 -inMemory -sharedDb"]) + // .withCommand(["-jar DynamoDBLocal.jar -inMemory -sharedDb"]) .withExposedPorts(TEST_DYNAMODB_PORT); /** From 8ebd7eaf6213cf829b9985ce81a4d53581c523cd Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 27 Aug 2024 16:44:59 +0200 Subject: [PATCH 013/241] Fix import --- .../test/catalogPlatformstateWriter.integration.test.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 8b77bd2433..786e0ca74c 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -55,11 +55,7 @@ import { toDocumentV1, toDescriptorV1, } from "pagopa-interop-commons-test/index.js"; -import { - readAllItems, - readCatalogEntry, - writeCatalogEntry, -} from "../src/utils.js"; +import { readCatalogEntry, writeCatalogEntry } from "../src/utils.js"; import { handleMessageV1 } from "../src/consumerServiceV1.js"; import { handleMessageV2 } from "../src/consumerServiceV2.js"; From 279aed36a6a1c63fec0f248f17d14d743612bdce Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 27 Aug 2024 16:45:07 +0200 Subject: [PATCH 014/241] Rename tables --- docker/docker-compose.yml | 2 +- packages/catalog-platformstate-writer/.env | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 832df08647..b0b04b4482 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -68,7 +68,7 @@ services: AWS_ACCESS_KEY_ID: keyid AWS_SECRET_ACCESS_KEY: key command: dynamodb create-table - --table-name PlatformStates + --table-name platform-states --attribute-definitions AttributeName=PK,AttributeType=S --key-schema diff --git a/packages/catalog-platformstate-writer/.env b/packages/catalog-platformstate-writer/.env index 6db18881a4..861e5be3cc 100644 --- a/packages/catalog-platformstate-writer/.env +++ b/packages/catalog-platformstate-writer/.env @@ -8,7 +8,7 @@ CATALOG_TOPIC="event-store.catalog.events" AWS_CONFIG_FILE=aws.config.local TOKEN_GENERATION_READMODEL_HOST="localhost" TOKEN_GENERATION_READMODEL_PORT=8085 -TOKEN_GENERATION_READMODEL_TABLE_NAME_PLATFORM="PlatformStates" -TOKEN_GENERATION_READMODEL_TABLE_NAME_TOKEN_GENERATION="TokenGenerationStates" +TOKEN_GENERATION_READMODEL_TABLE_NAME_PLATFORM="platform-states" +TOKEN_GENERATION_READMODEL_TABLE_NAME_TOKEN_GENERATION="token-generation-states" AWS_REGION="eu-central-1" From f5ccd5da5d7587859aab204e2ac3090f0ff2f227 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Wed, 28 Aug 2024 09:20:33 +0200 Subject: [PATCH 015/241] WIP: add platform-states logic --- .../src/consumerServiceV2.ts | 97 +++++++++++++++++-- .../catalog-platformstate-writer/src/utils.ts | 10 ++ 2 files changed, 101 insertions(+), 6 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index 6ad2e6b799..a28e39c2a6 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -1,12 +1,101 @@ import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; -import { EServiceEventEnvelopeV2 } from "pagopa-interop-models"; +import { + DescriptorState, + EServiceEventEnvelopeV2, + fromEServiceDescriptorStateV2, + genericInternalError, + ItemState, + PlatformStatesCatalogEntry, +} from "pagopa-interop-models"; import { match } from "ts-pattern"; +import { + deleteCatalogEntry, + descriptorStateToClientState, + readCatalogEntry, + writeCatalogEntry, +} from "./utils.js"; export async function handleMessageV2( message: EServiceEventEnvelopeV2, - _dynamoDBClient: DynamoDBClient + dynamoDBClient: DynamoDBClient ): Promise { await match(message) + .with({ type: "EServiceDescriptorPublished" }, async (msg) => { + const descriptorId = msg.data.descriptorId; + const eservice = msg.data.eservice; + if (!eservice) { + throw genericInternalError( + `EService not found in message data for event ${msg.type}` + ); + } + + const descriptor = eservice.descriptors.find( + (d) => d.id === descriptorId + ); + if (!descriptor) { + throw genericInternalError( + `Unable to find descriptor with id ${descriptorId}` + ); + } + const descriptorState: DescriptorState = fromEServiceDescriptorStateV2( + descriptor.state + ); + const catalogEntry: PlatformStatesCatalogEntry = { + // TODO: change with the PK type + PK: `ESERVICEDESCRIPTOR#${eservice.id}#${descriptorId}`, + state: descriptorStateToClientState(descriptorState), + descriptorAudience: descriptor.audience[0], + }; + + await writeCatalogEntry(catalogEntry, dynamoDBClient); + + // TODO: Add token-generation-states part + }) + .with( + { type: "EServiceDescriptorActivated" }, + { type: "EServiceDescriptorSuspended" }, + async (msg) => { + const eservice = msg.data.eservice; + if (!eservice) { + throw genericInternalError( + `EService not found in message data for event ${msg.type}` + ); + } + + // TODO: change with the PK type + const primaryKey = `ESERVICEDESCRIPTOR#${eservice.id}#${msg.data.descriptorId}`; + // TODO: remove read? + const catalogEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + + if (!catalogEntry) { + throw genericInternalError( + `Unable to find catalog entry with PK ${primaryKey}` + ); + } else { + const updatedCatalogEntry: PlatformStatesCatalogEntry = { + ...catalogEntry, + state: + msg.type === "EServiceDescriptorActivated" + ? ItemState.Enum.ACTIVE + : ItemState.Enum.INACTIVE, + }; + await writeCatalogEntry(updatedCatalogEntry, dynamoDBClient); + } + + // TODO: Add token-generation-states part + } + ) + .with({ type: "EServiceDescriptorArchived" }, async (msg) => { + const eservice = msg.data.eservice; + if (!eservice) { + throw genericInternalError( + `EService not found in message data for event ${msg.type}` + ); + } + + const primaryKey = `ESERVICEDESCRIPTOR#${eservice.id}#${msg.data.descriptorId}`; + await deleteCatalogEntry(primaryKey, dynamoDBClient); + }) .with( { type: "EServiceDeleted" }, { type: "EServiceAdded" }, @@ -16,10 +105,6 @@ export async function handleMessageV2( { type: "EServiceDraftDescriptorDeleted" }, { type: "EServiceDraftDescriptorUpdated" }, { type: "EServiceDescriptorQuotasUpdated" }, - { type: "EServiceDescriptorActivated" }, - { type: "EServiceDescriptorArchived" }, - { type: "EServiceDescriptorPublished" }, - { type: "EServiceDescriptorSuspended" }, { type: "EServiceDescriptorInterfaceAdded" }, { type: "EServiceDescriptorDocumentAdded" }, { type: "EServiceDescriptorInterfaceUpdated" }, diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index 8cdcf8d13b..d8c2e19002 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -1,5 +1,8 @@ import { + descriptorState, + DescriptorState, genericInternalError, + ItemState, PlatformStatesCatalogEntry, } from "pagopa-interop-models"; import { @@ -94,3 +97,10 @@ export const readAllItems = async ( const read: ScanCommandOutput = await dynamoDBClient.send(commandQuery); return read; }; + +export const descriptorStateToClientState = ( + state: DescriptorState +): ItemState => + state === descriptorState.published || state === descriptorState.deprecated + ? ItemState.Enum.ACTIVE + : ItemState.Enum.INACTIVE; From ac5abab1e1b2c3ca66b5f37b917aa3bc3c7dfde6 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Wed, 28 Aug 2024 10:08:58 +0200 Subject: [PATCH 016/241] Update docker-compose.yml --- docker/docker-compose.yml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index b0b04b4482..9d02556f87 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -60,9 +60,10 @@ services: ports: - 8085:8000 - token-generation-readmodel-db-init: + platform-states-table-init: depends_on: - token-generation-readmodel + restart: on-failure image: amazon/aws-cli environment: AWS_ACCESS_KEY_ID: keyid @@ -76,6 +77,23 @@ services: --billing-mode PAY_PER_REQUEST --endpoint-url http://token-generation-readmodel:8000 --region eu-central-1 + token-generation-states-table-init: + depends_on: + - token-generation-readmodel + restart: on-failure + image: amazon/aws-cli + environment: + AWS_ACCESS_KEY_ID: keyid + AWS_SECRET_ACCESS_KEY: key + command: dynamodb create-table + --table-name token-generation-states + --attribute-definitions + AttributeName=PK,AttributeType=S + --key-schema + AttributeName=PK,KeyType=HASH + --billing-mode PAY_PER_REQUEST + --endpoint-url http://token-generation-readmodel:8000 --region eu-central-1 + # Mongo Express is a web-based MongoDB admin interface, included for convenience mongo-express: image: mongo-express:1.0.2-20 From c399658b87daa0e9d9670c43de7f12e374bf1c74 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 28 Aug 2024 10:16:24 +0200 Subject: [PATCH 017/241] Fix import --- packages/commons-test/src/containerTestUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/commons-test/src/containerTestUtils.ts b/packages/commons-test/src/containerTestUtils.ts index ce7b6a948a..1ef35810a9 100644 --- a/packages/commons-test/src/containerTestUtils.ts +++ b/packages/commons-test/src/containerTestUtils.ts @@ -2,7 +2,6 @@ import { EventStoreConfig, ReadModelDbConfig, S3Config, - TokenGenerationReadModelDbConfig, } from "pagopa-interop-commons"; import { GenericContainer } from "testcontainers"; From aecb0146ddbd8f023aa5e2b53bf14e4b3458caf4 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 28 Aug 2024 10:43:43 +0200 Subject: [PATCH 018/241] Adjust tests --- ...logPlatformstateWriter.integration.test.ts | 52 +++---------------- 1 file changed, 6 insertions(+), 46 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 786e0ca74c..5338735951 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -43,9 +43,7 @@ import { PlatformStatesCatalogEntry, RiskAnalysis, descriptorState, - eserviceMode, generateId, - technology, toEServiceV2, } from "pagopa-interop-models"; import * as dynamodb from "@aws-sdk/client-dynamodb"; @@ -54,7 +52,10 @@ import { getMockValidRiskAnalysis, toDocumentV1, toDescriptorV1, -} from "pagopa-interop-commons-test/index.js"; + getMockDescriptor, + getMockEService, + getMockDocument, +} from "pagopa-interop-commons-test"; import { readCatalogEntry, writeCatalogEntry } from "../src/utils.js"; import { handleMessageV1 } from "../src/consumerServiceV1.js"; import { handleMessageV2 } from "../src/consumerServiceV2.js"; @@ -90,8 +91,8 @@ describe("database test", async () => { }; await dynamoDBClient.createTable(tokenGenerationTableDefinition); - const tablesResult = await dynamoDBClient.listTables(); - console.log(tablesResult.TableNames); + // const tablesResult = await dynamoDBClient.listTables(); + // console.log(tablesResult.TableNames); }); afterAll(async () => { const tableToDelete1: dynamodb.DeleteTableInput = { @@ -1410,44 +1411,3 @@ describe("database test", async () => { }); }); }); - -export const getMockEService = (): EService => ({ - id: generateId(), - name: "eservice name", - description: "eservice description", - createdAt: new Date(), - producerId: generateId(), - technology: technology.rest, - descriptors: [], - mode: eserviceMode.deliver, - riskAnalysis: [], -}); - -export const getMockDescriptor = (): Descriptor => ({ - id: generateId(), - version: "1", - docs: [], - state: descriptorState.draft, - audience: [], - voucherLifespan: 60, - dailyCallsPerConsumer: 10, - dailyCallsTotal: 1000, - createdAt: new Date(), - serverUrls: ["pagopa.it"], - agreementApprovalPolicy: "Automatic", - attributes: { - certified: [], - verified: [], - declared: [], - }, -}); - -export const getMockDocument = (): Document => ({ - name: "fileName", - path: "filePath", - id: generateId(), - prettyName: "prettyName", - contentType: "json", - checksum: "checksum", - uploadDate: new Date(), -}); From 1397e5f79185c27f6f711526acc08275bdb54b17 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Wed, 28 Aug 2024 11:02:02 +0200 Subject: [PATCH 019/241] Add catalog-platformstate-writer tests --- ...logPlatformstateWriter.integration.test.ts | 58 ++++++++++++++++--- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 786e0ca74c..8462f88f80 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -39,6 +39,7 @@ import { EServiceUpdatedV1, EServiceWithDescriptorsDeletedV1, EserviceAttributes, + ItemState, MovedAttributesFromEserviceToDescriptorsV1, PlatformStatesCatalogEntry, RiskAnalysis, @@ -903,6 +904,7 @@ describe("database test", async () => { it("EServiceDescriptorActivated", async () => { const suspendedDescriptor: Descriptor = { ...getMockDescriptor(), + audience: ["pagopa.it"], interface: getMockDocument(), state: descriptorState.suspended, publishedAt: new Date(), @@ -937,15 +939,27 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; + const primaryKey = `ESERVICEDESCRIPTOR#${updatedEService.id}#${publishedDescriptor.id}`; + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: ItemState.Enum.INACTIVE, + descriptorAudience: publishedDescriptor.audience[0], + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); await handleMessageV2(message, dynamoDBClient); - // TO DO - expect(1).toBe(1); + const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + const expectedEntry: PlatformStatesCatalogEntry = { + ...previousStateEntry, + state: ItemState.Enum.ACTIVE, + }; + expect(retrievedEntry).toEqual(expectedEntry); }); - it("EServiceDescriptorArchived", async () => { + it.only("EServiceDescriptorArchived", async () => { const publishedDescriptor: Descriptor = { ...getMockDescriptor(), + audience: ["pagopa.it"], interface: getMockDocument(), state: descriptorState.published, publishedAt: new Date(), @@ -978,15 +992,23 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; + const primaryKey = `ESERVICEDESCRIPTOR#${updatedEService.id}#${publishedDescriptor.id}`; + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: ItemState.Enum.INACTIVE, + descriptorAudience: publishedDescriptor.audience[0], + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); await handleMessageV2(message, dynamoDBClient); - // TO DO - expect(1).toBe(1); + const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + expect(retrievedEntry).toBeUndefined(); }); it("EServiceDescriptorPublished", async () => { const draftDescriptor: Descriptor = { ...getMockDescriptor(), + audience: ["pagopa.it"], interface: getMockDocument(), state: descriptorState.draft, }; @@ -1020,13 +1042,20 @@ describe("database test", async () => { }; await handleMessageV2(message, dynamoDBClient); - // TO DO - expect(1).toBe(1); + const primaryKey = `ESERVICEDESCRIPTOR#${updatedEService.id}#${publishedDescriptor.id}`; + const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + const expectedEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: ItemState.Enum.ACTIVE, + descriptorAudience: publishedDescriptor.audience[0], + }; + expect(retrievedEntry).toEqual(expectedEntry); }); it("EServiceDescriptorSuspended", async () => { const publishedDescriptor: Descriptor = { ...getMockDescriptor(), + audience: ["pagopa.it"], interface: getMockDocument(), state: descriptorState.published, publishedAt: new Date(), @@ -1059,10 +1088,21 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; + const primaryKey = `ESERVICEDESCRIPTOR#${updatedEService.id}#${publishedDescriptor.id}`; + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: ItemState.Enum.ACTIVE, + descriptorAudience: publishedDescriptor.audience[0], + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); await handleMessageV2(message, dynamoDBClient); - // TO DO - expect(1).toBe(1); + const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + const expectedEntry: PlatformStatesCatalogEntry = { + ...previousStateEntry, + state: ItemState.Enum.INACTIVE, + }; + expect(retrievedEntry).toEqual(expectedEntry); }); it("EServiceDescriptorInterfaceAdded", async () => { From 222ed09766c0107130b096f0968c52ec0cbac400 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Wed, 28 Aug 2024 11:09:53 +0200 Subject: [PATCH 020/241] Removed unused tests --- ...logPlatformstateWriter.integration.test.ts | 651 +----------------- 1 file changed, 1 insertion(+), 650 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 316ab7c980..8f053f29c3 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -5,44 +5,27 @@ import { ClonedEServiceAddedV1, Descriptor, Document, - DraftEServiceUpdatedV2, EService, EServiceAddedV1, - EServiceAddedV2, - EServiceClonedV2, EServiceDeletedV1, - EServiceDeletedV2, EServiceDescriptorActivatedV2, EServiceDescriptorAddedV1, - EServiceDescriptorAddedV2, EServiceDescriptorArchivedV2, - EServiceDescriptorDocumentAddedV2, - EServiceDescriptorDocumentDeletedV2, - EServiceDescriptorDocumentUpdatedV2, - EServiceDescriptorInterfaceAddedV2, - EServiceDescriptorInterfaceDeletedV2, - EServiceDescriptorInterfaceUpdatedV2, EServiceDescriptorPublishedV2, - EServiceDescriptorQuotasUpdatedV2, EServiceDescriptorSuspendedV2, EServiceDescriptorUpdatedV1, EServiceDocumentAddedV1, EServiceDocumentDeletedV1, EServiceDocumentUpdatedV1, - EServiceDraftDescriptorDeletedV2, - EServiceDraftDescriptorUpdatedV2, EServiceEventEnvelope, EServiceRiskAnalysisAddedV1, - EServiceRiskAnalysisAddedV2, EServiceRiskAnalysisDeletedV1, - EServiceRiskAnalysisDeletedV2, EServiceUpdatedV1, EServiceWithDescriptorsDeletedV1, EserviceAttributes, ItemState, MovedAttributesFromEserviceToDescriptorsV1, PlatformStatesCatalogEntry, - RiskAnalysis, descriptorState, generateId, toEServiceV2, @@ -109,22 +92,6 @@ describe("database test", async () => { }); describe("Events V1", async () => { const mockEService = getMockEService(); - it("sample", async () => { - const key = `ESERVICEDESCRIPTOR#${generateId()}#${generateId()}`; - - const catalogEntry: PlatformStatesCatalogEntry = { - PK: key, - state: "ACTIVE", - descriptorAudience: "pagopa.it", - }; - await writeCatalogEntry(catalogEntry, dynamoDBClient); - - const result = await readCatalogEntry(key, dynamoDBClient); - - // const resultFetchAll = await readAllItems(dynamoDBClient); - expect(result).toEqual(catalogEntry); - }); - it("EServiceAdded", async () => { const payload: EServiceAddedV1 = { eservice: toEServiceV1(mockEService), @@ -630,278 +597,6 @@ describe("database test", async () => { describe("Events V2", async () => { const mockEService = getMockEService(); - it("EServiceDeleted", async () => { - // await writeInReadmodel(toReadModelEService(mockEService), eservices, 1); - - const payload: EServiceDeletedV2 = { - eserviceId: mockEService.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDeleted", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceAdded", async () => { - const payload: EServiceAddedV2 = { - eservice: toEServiceV2(mockEService), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 1, - type: "EServiceAdded", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("DraftEServiceUpdated", async () => { - // await writeInReadmodel( - // toReadModelEService(mockEService), - // eservices, - // 1 - // ); - - const updatedEService: EService = { - ...mockEService, - description: "updated description", - }; - const payload: DraftEServiceUpdatedV2 = { - eservice: toEServiceV2(updatedEService), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "DraftEServiceUpdated", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceCloned", async () => { - const sourceDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.published, - publishedAt: new Date(), - interface: getMockDocument(), - }; - const sourceEService: EService = { - ...mockEService, - descriptors: [sourceDescriptor], - }; - // await writeInReadmodel( - // toReadModelEService(sourceEService), - // eservices, - // 1 - // ); - - const date = new Date(); - const clonedEService: EService = { - ...sourceEService, - id: generateId(), - createdAt: new Date(), - name: `${mockEService.name} - clone - ${format( - date, - "dd/MM/yyyy HH:mm:ss" - )}`, - descriptors: [ - { - ...sourceDescriptor, - publishedAt: undefined, - state: descriptorState.draft, - }, - ], - }; - - const payload: EServiceClonedV2 = { - sourceEservice: toEServiceV2(sourceEService), - sourceDescriptorId: sourceDescriptor.id, - eservice: toEServiceV2(clonedEService), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: clonedEService.id, - version: 1, - type: "EServiceCloned", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDescriptorAdded", async () => { - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - }; - const eservice: EService = { - ...mockEService, - descriptors: [], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedEService: EService = { - ...eservice, - descriptors: [draftDescriptor], - }; - const payload: EServiceDescriptorAddedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: draftDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorAdded", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDraftDescriptorDeleted", async () => { - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedEService: EService = { - ...eservice, - descriptors: [], - }; - const payload: EServiceDraftDescriptorDeletedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: draftDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDraftDescriptorDeleted", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDraftDescriptorUpdated", async () => { - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - interface: getMockDocument(), - state: descriptorState.draft, - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedDraftDescriptor: Descriptor = { - ...draftDescriptor, - description: "updated description", - }; - const updatedEService: EService = { - ...eservice, - descriptors: [updatedDraftDescriptor], - }; - const payload: EServiceDraftDescriptorUpdatedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: updatedDraftDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDraftDescriptorUpdated", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDescriptorQuotasUpdated", async () => { - const publishedDescriptor: Descriptor = { - ...getMockDescriptor(), - interface: getMockDocument(), - state: descriptorState.published, - publishedAt: new Date(), - }; - const eservice: EService = { - ...mockEService, - descriptors: [publishedDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedPublishedDescriptor: Descriptor = { - ...publishedDescriptor, - dailyCallsTotal: publishedDescriptor.dailyCallsTotal + 1000, - }; - const updatedEService: EService = { - ...eservice, - descriptors: [updatedPublishedDescriptor], - }; - const payload: EServiceDescriptorQuotasUpdatedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: updatedPublishedDescriptor.id, - }; - - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorQuotasUpdated", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - it("EServiceDescriptorActivated", async () => { const suspendedDescriptor: Descriptor = { ...getMockDescriptor(), @@ -957,7 +652,7 @@ describe("database test", async () => { expect(retrievedEntry).toEqual(expectedEntry); }); - it.only("EServiceDescriptorArchived", async () => { + it("EServiceDescriptorArchived", async () => { const publishedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], @@ -1105,349 +800,5 @@ describe("database test", async () => { }; expect(retrievedEntry).toEqual(expectedEntry); }); - - it("EServiceDescriptorInterfaceAdded", async () => { - const descriptorInterface = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - docs: [], - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - const updatedEService: EService = { - ...eservice, - descriptors: [{ ...draftDescriptor, interface: descriptorInterface }], - }; - const payload: EServiceDescriptorInterfaceAddedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: draftDescriptor.id, - documentId: descriptorInterface.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorInterfaceAdded", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDescriptorDocumentAdded", async () => { - const document = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - docs: [], - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedEService: EService = { - ...eservice, - descriptors: [{ ...draftDescriptor, docs: [document] }], - }; - const payload: EServiceDescriptorDocumentAddedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: draftDescriptor.id, - documentId: document.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorDocumentAdded", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDescriptorInterfaceUpdated", async () => { - const descriptorInterface = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - interface: descriptorInterface, - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedInterface: Document = { - ...descriptorInterface, - prettyName: "updated pretty name", - }; - const updatedEService: EService = { - ...eservice, - descriptors: [{ ...draftDescriptor, interface: updatedInterface }], - }; - const payload: EServiceDescriptorInterfaceUpdatedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: draftDescriptor.id, - documentId: updatedInterface.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorInterfaceUpdated", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDescriptorDocumentUpdated", async () => { - const document = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - docs: [document], - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedDocument: Document = { - ...document, - prettyName: "updated pretty name", - }; - const updatedEService: EService = { - ...eservice, - descriptors: [{ ...draftDescriptor, docs: [updatedDocument] }], - }; - const payload: EServiceDescriptorDocumentUpdatedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: draftDescriptor.id, - documentId: document.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorDocumentUpdated", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDescriptorInterfaceDeleted", async () => { - const descriptorInterface = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - interface: descriptorInterface, - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedEService: EService = { - ...eservice, - descriptors: [ - { ...draftDescriptor, serverUrls: [], interface: undefined }, - ], - }; - const payload: EServiceDescriptorInterfaceDeletedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: draftDescriptor.id, - documentId: descriptorInterface.id, - }; - - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorInterfaceDeleted", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDescriptorDocumentDeleted", async () => { - const document = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - docs: [document], - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedEService: EService = { - ...eservice, - descriptors: [{ ...draftDescriptor, docs: [] }], - }; - const payload: EServiceDescriptorDocumentDeletedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: draftDescriptor.id, - documentId: document.id, - }; - - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorDocumentDeleted", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceRiskAnalysisAdded", async () => { - // await writeInReadmodel( - // toReadModelEService(mockEService), - // eservices, - // 1 - // ); - - const mockRiskAnalysis = getMockValidRiskAnalysis("PA"); - const updatedEService: EService = { - ...mockEService, - riskAnalysis: [mockRiskAnalysis], - }; - const payload: EServiceRiskAnalysisAddedV2 = { - eservice: toEServiceV2(updatedEService), - riskAnalysisId: mockRiskAnalysis.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceRiskAnalysisAdded", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceRiskAnalysisUpdated", async () => { - const mockRiskAnalysis = getMockValidRiskAnalysis("PA"); - const eservice: EService = { - ...mockEService, - riskAnalysis: [mockRiskAnalysis], - }; - // await writeInReadmodel( - // toReadModelEService(eservice), - // eservices, - // 1 - // ); - - const updatedRiskAnalysis: RiskAnalysis = { - ...mockRiskAnalysis, - riskAnalysisForm: { - ...mockRiskAnalysis.riskAnalysisForm, - singleAnswers: mockRiskAnalysis.riskAnalysisForm.singleAnswers.map( - (singleAnswer) => ({ - ...singleAnswer, - value: - singleAnswer.key === "purpose" ? "OTHER" : singleAnswer.value, - }) - ), - }, - }; - const updatedEService: EService = { - ...eservice, - riskAnalysis: [updatedRiskAnalysis], - }; - const payload: EServiceRiskAnalysisAddedV2 = { - eservice: toEServiceV2(updatedEService), - riskAnalysisId: mockRiskAnalysis.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceRiskAnalysisUpdated", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceRiskAnalysisDeleted", async () => { - const riskAnalysis = getMockValidRiskAnalysis("PA"); - // const eservice: EService = { - // ...mockEService, - // riskAnalysis: [riskAnalysis], - // }; - - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedEService: EService = { - ...mockEService, - riskAnalysis: [], - }; - const payload: EServiceRiskAnalysisDeletedV2 = { - eservice: toEServiceV2(updatedEService), - riskAnalysisId: riskAnalysis.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceRiskAnalysisDeleted", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); }); }); From 46ea9e238c97f5a1e09b6d34c0498363dba06862 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 28 Aug 2024 11:49:28 +0200 Subject: [PATCH 021/241] Adjust test placeholder --- ...logPlatformstateWriter.integration.test.ts | 1413 ----------------- .../test/sample.test.ts | 7 + 2 files changed, 7 insertions(+), 1413 deletions(-) delete mode 100644 packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts create mode 100644 packages/catalog-platformstate-writer/test/sample.test.ts diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts deleted file mode 100644 index 5338735951..0000000000 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ /dev/null @@ -1,1413 +0,0 @@ -import { format } from "util"; -import { afterAll, beforeAll, describe, expect, it } from "vitest"; -import { - AttributeId, - ClonedEServiceAddedV1, - Descriptor, - Document, - DraftEServiceUpdatedV2, - EService, - EServiceAddedV1, - EServiceAddedV2, - EServiceClonedV2, - EServiceDeletedV1, - EServiceDeletedV2, - EServiceDescriptorActivatedV2, - EServiceDescriptorAddedV1, - EServiceDescriptorAddedV2, - EServiceDescriptorArchivedV2, - EServiceDescriptorDocumentAddedV2, - EServiceDescriptorDocumentDeletedV2, - EServiceDescriptorDocumentUpdatedV2, - EServiceDescriptorInterfaceAddedV2, - EServiceDescriptorInterfaceDeletedV2, - EServiceDescriptorInterfaceUpdatedV2, - EServiceDescriptorPublishedV2, - EServiceDescriptorQuotasUpdatedV2, - EServiceDescriptorSuspendedV2, - EServiceDescriptorUpdatedV1, - EServiceDocumentAddedV1, - EServiceDocumentDeletedV1, - EServiceDocumentUpdatedV1, - EServiceDraftDescriptorDeletedV2, - EServiceDraftDescriptorUpdatedV2, - EServiceEventEnvelope, - EServiceRiskAnalysisAddedV1, - EServiceRiskAnalysisAddedV2, - EServiceRiskAnalysisDeletedV1, - EServiceRiskAnalysisDeletedV2, - EServiceUpdatedV1, - EServiceWithDescriptorsDeletedV1, - EserviceAttributes, - MovedAttributesFromEserviceToDescriptorsV1, - PlatformStatesCatalogEntry, - RiskAnalysis, - descriptorState, - generateId, - toEServiceV2, -} from "pagopa-interop-models"; -import * as dynamodb from "@aws-sdk/client-dynamodb"; -import { - toEServiceV1, - getMockValidRiskAnalysis, - toDocumentV1, - toDescriptorV1, - getMockDescriptor, - getMockEService, - getMockDocument, -} from "pagopa-interop-commons-test"; -import { readCatalogEntry, writeCatalogEntry } from "../src/utils.js"; -import { handleMessageV1 } from "../src/consumerServiceV1.js"; -import { handleMessageV2 } from "../src/consumerServiceV2.js"; - -import { config } from "./utils.js"; - -describe("database test", async () => { - const dynamoDBClient = new dynamodb.DynamoDB({ - credentials: { accessKeyId: "key", secretAccessKey: "secret" }, - region: "eu-central-1", - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - endpoint: `http://${config!.tokenGenerationReadModelDbHost}:${ - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - config!.tokenGenerationReadModelDbPort - }`, - }); - beforeAll(async () => { - const platformTableDefinition: dynamodb.CreateTableInput = { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - TableName: config!.tokenGenerationReadModelTableNamePlatform, - AttributeDefinitions: [{ AttributeName: "PK", AttributeType: "S" }], - KeySchema: [{ AttributeName: "PK", KeyType: "HASH" }], - BillingMode: "PAY_PER_REQUEST", - }; - await dynamoDBClient.createTable(platformTableDefinition); - - const tokenGenerationTableDefinition: dynamodb.CreateTableInput = { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - TableName: config!.tokenGenerationReadModelTableNameTokenGeneration, - AttributeDefinitions: [{ AttributeName: "PK", AttributeType: "S" }], - KeySchema: [{ AttributeName: "PK", KeyType: "HASH" }], - BillingMode: "PAY_PER_REQUEST", - }; - await dynamoDBClient.createTable(tokenGenerationTableDefinition); - - // const tablesResult = await dynamoDBClient.listTables(); - // console.log(tablesResult.TableNames); - }); - afterAll(async () => { - const tableToDelete1: dynamodb.DeleteTableInput = { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - TableName: config!.tokenGenerationReadModelTableNamePlatform, - }; - const tableToDelete2: dynamodb.DeleteTableInput = { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - TableName: config!.tokenGenerationReadModelTableNameTokenGeneration, - }; - await dynamoDBClient.deleteTable(tableToDelete1); - await dynamoDBClient.deleteTable(tableToDelete2); - }); - describe("Events V1", async () => { - const mockEService = getMockEService(); - it("sample", async () => { - const key = `ESERVICEDESCRIPTOR#${generateId()}#${generateId()}`; - - const catalogEntry: PlatformStatesCatalogEntry = { - PK: key, - state: "ACTIVE", - descriptorAudience: "pagopa.it", - }; - await writeCatalogEntry(catalogEntry, dynamoDBClient); - - const result = await readCatalogEntry(key, dynamoDBClient); - - // const resultFetchAll = await readAllItems(dynamoDBClient); - expect(result).toEqual(catalogEntry); - }); - - it("EServiceAdded", async () => { - const payload: EServiceAddedV1 = { - eservice: toEServiceV1(mockEService), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 1, - type: "EServiceAdded", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("ClonedEServiceAdded", async () => { - // await writeInReadmodel( - // toReadModelEService(mockEService), - // eservices, - // 1 - // ); - - const date = new Date(); - const clonedEService: EService = { - ...mockEService, - id: generateId(), - createdAt: new Date(), - name: `${mockEService.name} - clone - ${format( - date, - "dd/MM/yyyy HH:mm:ss" - )}`, - }; - - const payload: ClonedEServiceAddedV1 = { - eservice: toEServiceV1(clonedEService), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: clonedEService.id, - version: 1, - type: "ClonedEServiceAdded", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceUpdated", async () => { - // await writeInReadmodel( - // toReadModelEService(mockEService), - // eservices, - // 1 - // ); - - const updatedEService: EService = { - ...mockEService, - description: "updated description", - }; - const payload: EServiceUpdatedV1 = { - eservice: toEServiceV1(updatedEService), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceUpdated", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceRiskAnalysisAdded", async () => { - // await writeInReadmodel( - // toReadModelEService(mockEService), - // eservices, - // 1 - // ); - - const mockRiskAnalysis = getMockValidRiskAnalysis("PA"); - const updatedEService: EService = { - ...mockEService, - riskAnalysis: [...mockEService.riskAnalysis, mockRiskAnalysis], - }; - const payload: EServiceRiskAnalysisAddedV1 = { - eservice: toEServiceV1(updatedEService), - riskAnalysisId: mockRiskAnalysis.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceRiskAnalysisAdded", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("MovedAttributesFromEserviceToDescriptors", async () => { - const attributes: EserviceAttributes = { - certified: [ - [ - { - id: generateId(), - explicitAttributeVerification: false, - }, - ], - ], - declared: [], - verified: [], - }; - const descriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - attributes, - }; - // const eservice: EService = { - // ...mockEService, - // attributes, - // descriptors: [descriptor], - // }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - const updatedDescriptor = { - ...descriptor, - attributes, - }; - const updatedEService: EService = { - ...mockEService, - attributes: undefined, - descriptors: [updatedDescriptor], - }; - const payload: MovedAttributesFromEserviceToDescriptorsV1 = { - eservice: toEServiceV1(updatedEService), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "MovedAttributesFromEserviceToDescriptors", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceWithDescriptorsDeleted", async () => { - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedEService: EService = { - ...eservice, - descriptors: [], - }; - const payload: EServiceWithDescriptorsDeletedV1 = { - eservice: toEServiceV1(updatedEService), - descriptorId: draftDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceWithDescriptorsDeleted", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDocumentUpdated", async () => { - const document = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - docs: [document], - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedDocument: Document = { - ...document, - prettyName: "updated pretty name", - }; - - const payload: EServiceDocumentUpdatedV1 = { - eserviceId: eservice.id, - descriptorId: draftDescriptor.id, - documentId: document.id, - serverUrls: [], - updatedDocument: toDocumentV1(updatedDocument), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDocumentUpdated", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDeleted", async () => { - // await writeInReadmodel(toReadModelEService(mockEService), eservices, 1); - - const payload: EServiceDeletedV1 = { - eserviceId: mockEService.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDeleted", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - describe("EServiceDocumentAdded", () => { - it("interface", async () => { - const descriptorInterface = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - docs: [], - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const payload: EServiceDocumentAddedV1 = { - eserviceId: eservice.id, - descriptorId: draftDescriptor.id, - serverUrls: ["pagopa.it"], - document: toDocumentV1(descriptorInterface), - isInterface: true, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDocumentAdded", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("document", async () => { - const document = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - docs: [], - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const payload: EServiceDocumentAddedV1 = { - eserviceId: eservice.id, - descriptorId: draftDescriptor.id, - serverUrls: [], - document: toDocumentV1(document), - isInterface: false, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDocumentAdded", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - }); - - describe("EServiceDocumentDeleted", () => { - it("interface", async () => { - const descriptorInterface = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - interface: descriptorInterface, - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const payload: EServiceDocumentDeletedV1 = { - eserviceId: eservice.id, - descriptorId: draftDescriptor.id, - documentId: descriptorInterface.id, - }; - - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDocumentDeleted", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("document", async () => { - const document = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - docs: [document], - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const payload: EServiceDocumentDeletedV1 = { - eserviceId: eservice.id, - descriptorId: draftDescriptor.id, - documentId: document.id, - }; - - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDocumentDeleted", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - }); - - it("EServiceDescriptorAdded", async () => { - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - }; - const eservice: EService = { - ...mockEService, - descriptors: [], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const payload: EServiceDescriptorAddedV1 = { - eserviceId: eservice.id, - eserviceDescriptor: toDescriptorV1(draftDescriptor), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorAdded", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDescriptorUpdated", async () => { - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - interface: getMockDocument(), - state: descriptorState.draft, - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const publishedDescriptor: Descriptor = { - ...draftDescriptor, - publishedAt: new Date(), - state: descriptorState.published, - }; - - const payload: EServiceDescriptorUpdatedV1 = { - eserviceId: eservice.id, - eserviceDescriptor: toDescriptorV1(publishedDescriptor), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorUpdated", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceRiskAnalysisDeleted", async () => { - const riskAnalysis = getMockValidRiskAnalysis("PA"); - // const eservice: EService = { - // ...mockEService, - // riskAnalysis: [riskAnalysis], - // }; - - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedEService: EService = { - ...mockEService, - riskAnalysis: [], - }; - const payload: EServiceRiskAnalysisDeletedV1 = { - eservice: toEServiceV1(updatedEService), - riskAnalysisId: riskAnalysis.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceRiskAnalysisDeleted", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - }); - - describe("Events V2", async () => { - const mockEService = getMockEService(); - it("EServiceDeleted", async () => { - // await writeInReadmodel(toReadModelEService(mockEService), eservices, 1); - - const payload: EServiceDeletedV2 = { - eserviceId: mockEService.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDeleted", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceAdded", async () => { - const payload: EServiceAddedV2 = { - eservice: toEServiceV2(mockEService), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 1, - type: "EServiceAdded", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("DraftEServiceUpdated", async () => { - // await writeInReadmodel( - // toReadModelEService(mockEService), - // eservices, - // 1 - // ); - - const updatedEService: EService = { - ...mockEService, - description: "updated description", - }; - const payload: DraftEServiceUpdatedV2 = { - eservice: toEServiceV2(updatedEService), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "DraftEServiceUpdated", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceCloned", async () => { - const sourceDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.published, - publishedAt: new Date(), - interface: getMockDocument(), - }; - const sourceEService: EService = { - ...mockEService, - descriptors: [sourceDescriptor], - }; - // await writeInReadmodel( - // toReadModelEService(sourceEService), - // eservices, - // 1 - // ); - - const date = new Date(); - const clonedEService: EService = { - ...sourceEService, - id: generateId(), - createdAt: new Date(), - name: `${mockEService.name} - clone - ${format( - date, - "dd/MM/yyyy HH:mm:ss" - )}`, - descriptors: [ - { - ...sourceDescriptor, - publishedAt: undefined, - state: descriptorState.draft, - }, - ], - }; - - const payload: EServiceClonedV2 = { - sourceEservice: toEServiceV2(sourceEService), - sourceDescriptorId: sourceDescriptor.id, - eservice: toEServiceV2(clonedEService), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: clonedEService.id, - version: 1, - type: "EServiceCloned", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDescriptorAdded", async () => { - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - }; - const eservice: EService = { - ...mockEService, - descriptors: [], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedEService: EService = { - ...eservice, - descriptors: [draftDescriptor], - }; - const payload: EServiceDescriptorAddedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: draftDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorAdded", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDraftDescriptorDeleted", async () => { - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedEService: EService = { - ...eservice, - descriptors: [], - }; - const payload: EServiceDraftDescriptorDeletedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: draftDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDraftDescriptorDeleted", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDraftDescriptorUpdated", async () => { - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - interface: getMockDocument(), - state: descriptorState.draft, - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedDraftDescriptor: Descriptor = { - ...draftDescriptor, - description: "updated description", - }; - const updatedEService: EService = { - ...eservice, - descriptors: [updatedDraftDescriptor], - }; - const payload: EServiceDraftDescriptorUpdatedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: updatedDraftDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDraftDescriptorUpdated", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDescriptorQuotasUpdated", async () => { - const publishedDescriptor: Descriptor = { - ...getMockDescriptor(), - interface: getMockDocument(), - state: descriptorState.published, - publishedAt: new Date(), - }; - const eservice: EService = { - ...mockEService, - descriptors: [publishedDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedPublishedDescriptor: Descriptor = { - ...publishedDescriptor, - dailyCallsTotal: publishedDescriptor.dailyCallsTotal + 1000, - }; - const updatedEService: EService = { - ...eservice, - descriptors: [updatedPublishedDescriptor], - }; - const payload: EServiceDescriptorQuotasUpdatedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: updatedPublishedDescriptor.id, - }; - - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorQuotasUpdated", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDescriptorActivated", async () => { - const suspendedDescriptor: Descriptor = { - ...getMockDescriptor(), - interface: getMockDocument(), - state: descriptorState.suspended, - publishedAt: new Date(), - suspendedAt: new Date(), - }; - const eservice: EService = { - ...mockEService, - descriptors: [suspendedDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const publishedDescriptor: Descriptor = { - ...suspendedDescriptor, - publishedAt: new Date(), - suspendedAt: new Date(), - state: descriptorState.published, - }; - const updatedEService: EService = { - ...eservice, - descriptors: [publishedDescriptor], - }; - const payload: EServiceDescriptorActivatedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: publishedDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorActivated", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDescriptorArchived", async () => { - const publishedDescriptor: Descriptor = { - ...getMockDescriptor(), - interface: getMockDocument(), - state: descriptorState.published, - publishedAt: new Date(), - }; - const eservice: EService = { - ...mockEService, - descriptors: [publishedDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const archivedDescriptor: Descriptor = { - ...publishedDescriptor, - archivedAt: new Date(), - state: descriptorState.archived, - }; - const updatedEService: EService = { - ...eservice, - descriptors: [archivedDescriptor], - }; - const payload: EServiceDescriptorArchivedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: archivedDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorArchived", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDescriptorPublished", async () => { - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - interface: getMockDocument(), - state: descriptorState.draft, - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const publishedDescriptor: Descriptor = { - ...draftDescriptor, - publishedAt: new Date(), - state: descriptorState.published, - }; - const updatedEService: EService = { - ...eservice, - descriptors: [publishedDescriptor], - }; - const payload: EServiceDescriptorPublishedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: publishedDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorPublished", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDescriptorSuspended", async () => { - const publishedDescriptor: Descriptor = { - ...getMockDescriptor(), - interface: getMockDocument(), - state: descriptorState.published, - publishedAt: new Date(), - }; - const eservice: EService = { - ...mockEService, - descriptors: [publishedDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const suspendedDescriptor: Descriptor = { - ...publishedDescriptor, - suspendedAt: new Date(), - state: descriptorState.suspended, - }; - const updatedEService: EService = { - ...eservice, - descriptors: [suspendedDescriptor], - }; - const payload: EServiceDescriptorSuspendedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: suspendedDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorSuspended", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDescriptorInterfaceAdded", async () => { - const descriptorInterface = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - docs: [], - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - const updatedEService: EService = { - ...eservice, - descriptors: [{ ...draftDescriptor, interface: descriptorInterface }], - }; - const payload: EServiceDescriptorInterfaceAddedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: draftDescriptor.id, - documentId: descriptorInterface.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorInterfaceAdded", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDescriptorDocumentAdded", async () => { - const document = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - docs: [], - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedEService: EService = { - ...eservice, - descriptors: [{ ...draftDescriptor, docs: [document] }], - }; - const payload: EServiceDescriptorDocumentAddedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: draftDescriptor.id, - documentId: document.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorDocumentAdded", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDescriptorInterfaceUpdated", async () => { - const descriptorInterface = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - interface: descriptorInterface, - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedInterface: Document = { - ...descriptorInterface, - prettyName: "updated pretty name", - }; - const updatedEService: EService = { - ...eservice, - descriptors: [{ ...draftDescriptor, interface: updatedInterface }], - }; - const payload: EServiceDescriptorInterfaceUpdatedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: draftDescriptor.id, - documentId: updatedInterface.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorInterfaceUpdated", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDescriptorDocumentUpdated", async () => { - const document = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - docs: [document], - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedDocument: Document = { - ...document, - prettyName: "updated pretty name", - }; - const updatedEService: EService = { - ...eservice, - descriptors: [{ ...draftDescriptor, docs: [updatedDocument] }], - }; - const payload: EServiceDescriptorDocumentUpdatedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: draftDescriptor.id, - documentId: document.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorDocumentUpdated", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDescriptorInterfaceDeleted", async () => { - const descriptorInterface = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - interface: descriptorInterface, - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedEService: EService = { - ...eservice, - descriptors: [ - { ...draftDescriptor, serverUrls: [], interface: undefined }, - ], - }; - const payload: EServiceDescriptorInterfaceDeletedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: draftDescriptor.id, - documentId: descriptorInterface.id, - }; - - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorInterfaceDeleted", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDescriptorDocumentDeleted", async () => { - const document = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - docs: [document], - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedEService: EService = { - ...eservice, - descriptors: [{ ...draftDescriptor, docs: [] }], - }; - const payload: EServiceDescriptorDocumentDeletedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: draftDescriptor.id, - documentId: document.id, - }; - - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorDocumentDeleted", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceRiskAnalysisAdded", async () => { - // await writeInReadmodel( - // toReadModelEService(mockEService), - // eservices, - // 1 - // ); - - const mockRiskAnalysis = getMockValidRiskAnalysis("PA"); - const updatedEService: EService = { - ...mockEService, - riskAnalysis: [mockRiskAnalysis], - }; - const payload: EServiceRiskAnalysisAddedV2 = { - eservice: toEServiceV2(updatedEService), - riskAnalysisId: mockRiskAnalysis.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceRiskAnalysisAdded", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceRiskAnalysisUpdated", async () => { - const mockRiskAnalysis = getMockValidRiskAnalysis("PA"); - const eservice: EService = { - ...mockEService, - riskAnalysis: [mockRiskAnalysis], - }; - // await writeInReadmodel( - // toReadModelEService(eservice), - // eservices, - // 1 - // ); - - const updatedRiskAnalysis: RiskAnalysis = { - ...mockRiskAnalysis, - riskAnalysisForm: { - ...mockRiskAnalysis.riskAnalysisForm, - singleAnswers: mockRiskAnalysis.riskAnalysisForm.singleAnswers.map( - (singleAnswer) => ({ - ...singleAnswer, - value: - singleAnswer.key === "purpose" ? "OTHER" : singleAnswer.value, - }) - ), - }, - }; - const updatedEService: EService = { - ...eservice, - riskAnalysis: [updatedRiskAnalysis], - }; - const payload: EServiceRiskAnalysisAddedV2 = { - eservice: toEServiceV2(updatedEService), - riskAnalysisId: mockRiskAnalysis.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceRiskAnalysisUpdated", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceRiskAnalysisDeleted", async () => { - const riskAnalysis = getMockValidRiskAnalysis("PA"); - // const eservice: EService = { - // ...mockEService, - // riskAnalysis: [riskAnalysis], - // }; - - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedEService: EService = { - ...mockEService, - riskAnalysis: [], - }; - const payload: EServiceRiskAnalysisDeletedV2 = { - eservice: toEServiceV2(updatedEService), - riskAnalysisId: riskAnalysis.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceRiskAnalysisDeleted", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - }); -}); diff --git a/packages/catalog-platformstate-writer/test/sample.test.ts b/packages/catalog-platformstate-writer/test/sample.test.ts new file mode 100644 index 0000000000..a0769ee33d --- /dev/null +++ b/packages/catalog-platformstate-writer/test/sample.test.ts @@ -0,0 +1,7 @@ +import { describe, expect, it } from "vitest"; + +describe(() => { + it("sample", () => { + expect(1).toBe(1); + }); +}); From e5e896596f297a7f47b01e1d182b94823823e428 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Wed, 28 Aug 2024 15:15:34 +0200 Subject: [PATCH 022/241] Add logics for events v1 --- .../src/consumerServiceV1.ts | 62 ++++++++++++++++++- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index b85992c6cd..fbccfe0001 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -1,12 +1,69 @@ import { match } from "ts-pattern"; -import { EServiceEventEnvelopeV1 } from "pagopa-interop-models"; +import { + descriptorState, + EServiceEventEnvelopeV1, + fromDescriptorV1, + genericInternalError, + PlatformStatesCatalogEntry, +} from "pagopa-interop-models"; import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; +import { + deleteCatalogEntry, + descriptorStateToClientState, + writeCatalogEntry, +} from "./utils.js"; export async function handleMessageV1( message: EServiceEventEnvelopeV1, - _dynamoDBClient: DynamoDBClient + dynamoDBClient: DynamoDBClient ): Promise { await match(message) + // EServiceDescriptorPublished -> EServiceDescriptorUpdated + // EServiceDescriptorActivated,EServiceDescriptorSuspended -> EServiceDescriptorUpdated + // EServiceDescriptorArchived -> EServiceDescriptorUpdated + .with({ type: "EServiceDescriptorUpdated" }, async (msg) => { + const eserviceId = msg.data.eserviceId; + const descriptorV1 = msg.data.eserviceDescriptor; + if (!descriptorV1) { + throw genericInternalError( + `EServiceDescriptor not found in message data for event ${msg.type}` + ); + } + const descriptor = fromDescriptorV1(descriptorV1); + + match(descriptor.state) + .with( + descriptorState.published, + descriptorState.suspended, + async () => { + console.log(descriptor.state); + const catalogEntry: PlatformStatesCatalogEntry = { + // TODO: change with the PK type + PK: `ESERVICEDESCRIPTOR#${eserviceId}#${descriptor.id}`, + state: descriptorStateToClientState(descriptor.state), + descriptorAudience: descriptor.audience[0], + }; + console.log(catalogEntry); + await writeCatalogEntry(catalogEntry, dynamoDBClient); + } + ) + .with(descriptorState.archived, async () => { + const eserviceId = msg.data.eserviceId; + const descriptorV1 = msg.data.eserviceDescriptor; + if (!descriptorV1) { + throw genericInternalError( + `EServiceDescriptor not found in message data for event ${msg.type}` + ); + } + const descriptor = fromDescriptorV1(descriptorV1); + + const primaryKey = `ESERVICEDESCRIPTOR#${eserviceId}#${descriptor.id}`; + await deleteCatalogEntry(primaryKey, dynamoDBClient); + }) + .with(descriptorState.draft, descriptorState.deprecated, () => + Promise.resolve() + ); + }) .with( { type: "EServiceAdded" }, { type: "ClonedEServiceAdded" }, @@ -20,7 +77,6 @@ export async function handleMessageV1( { type: "EServiceDocumentAdded" }, { type: "EServiceDocumentDeleted" }, { type: "EServiceDescriptorAdded" }, - { type: "EServiceDescriptorUpdated" }, { type: "EServiceRiskAnalysisDeleted" }, () => Promise.resolve() ) From 5b1a9f6cfd51a908df3c616f1d6b2aa4ceb8352b Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Wed, 28 Aug 2024 15:15:48 +0200 Subject: [PATCH 023/241] Add tests v1 --- ...logPlatformstateWriter.integration.test.ts | 551 ++++-------------- 1 file changed, 111 insertions(+), 440 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 8f053f29c3..dd2cbaa3c4 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -1,40 +1,20 @@ -import { format } from "util"; -import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { - AttributeId, - ClonedEServiceAddedV1, Descriptor, - Document, EService, - EServiceAddedV1, - EServiceDeletedV1, EServiceDescriptorActivatedV2, - EServiceDescriptorAddedV1, EServiceDescriptorArchivedV2, EServiceDescriptorPublishedV2, EServiceDescriptorSuspendedV2, EServiceDescriptorUpdatedV1, - EServiceDocumentAddedV1, - EServiceDocumentDeletedV1, - EServiceDocumentUpdatedV1, EServiceEventEnvelope, - EServiceRiskAnalysisAddedV1, - EServiceRiskAnalysisDeletedV1, - EServiceUpdatedV1, - EServiceWithDescriptorsDeletedV1, - EserviceAttributes, ItemState, - MovedAttributesFromEserviceToDescriptorsV1, PlatformStatesCatalogEntry, descriptorState, - generateId, toEServiceV2, } from "pagopa-interop-models"; import * as dynamodb from "@aws-sdk/client-dynamodb"; import { - toEServiceV1, - getMockValidRiskAnalysis, - toDocumentV1, toDescriptorV1, getMockDescriptor, getMockEService, @@ -56,7 +36,7 @@ describe("database test", async () => { config!.tokenGenerationReadModelDbPort }`, }); - beforeAll(async () => { + beforeEach(async () => { const platformTableDefinition: dynamodb.CreateTableInput = { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion TableName: config!.tokenGenerationReadModelTableNamePlatform, @@ -65,7 +45,6 @@ describe("database test", async () => { BillingMode: "PAY_PER_REQUEST", }; await dynamoDBClient.createTable(platformTableDefinition); - const tokenGenerationTableDefinition: dynamodb.CreateTableInput = { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion TableName: config!.tokenGenerationReadModelTableNameTokenGeneration, @@ -78,7 +57,7 @@ describe("database test", async () => { // const tablesResult = await dynamoDBClient.listTables(); // console.log(tablesResult.TableNames); }); - afterAll(async () => { + afterEach(async () => { const tableToDelete1: dynamodb.DeleteTableInput = { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion TableName: config!.tokenGenerationReadModelTableNamePlatform, @@ -91,511 +70,203 @@ describe("database test", async () => { await dynamoDBClient.deleteTable(tableToDelete2); }); describe("Events V1", async () => { - const mockEService = getMockEService(); - it("EServiceAdded", async () => { - const payload: EServiceAddedV1 = { - eservice: toEServiceV1(mockEService), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 1, - type: "EServiceAdded", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("ClonedEServiceAdded", async () => { - // await writeInReadmodel( - // toReadModelEService(mockEService), - // eservices, - // 1 - // ); - - const date = new Date(); - const clonedEService: EService = { - ...mockEService, - id: generateId(), - createdAt: new Date(), - name: `${mockEService.name} - clone - ${format( - date, - "dd/MM/yyyy HH:mm:ss" - )}`, - }; - - const payload: ClonedEServiceAddedV1 = { - eservice: toEServiceV1(clonedEService), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: clonedEService.id, - version: 1, - type: "ClonedEServiceAdded", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceUpdated", async () => { - // await writeInReadmodel( - // toReadModelEService(mockEService), - // eservices, - // 1 - // ); - - const updatedEService: EService = { - ...mockEService, - description: "updated description", - }; - const payload: EServiceUpdatedV1 = { - eservice: toEServiceV1(updatedEService), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceUpdated", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceRiskAnalysisAdded", async () => { - // await writeInReadmodel( - // toReadModelEService(mockEService), - // eservices, - // 1 - // ); - - const mockRiskAnalysis = getMockValidRiskAnalysis("PA"); - const updatedEService: EService = { - ...mockEService, - riskAnalysis: [...mockEService.riskAnalysis, mockRiskAnalysis], - }; - const payload: EServiceRiskAnalysisAddedV1 = { - eservice: toEServiceV1(updatedEService), - riskAnalysisId: mockRiskAnalysis.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceRiskAnalysisAdded", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("MovedAttributesFromEserviceToDescriptors", async () => { - const attributes: EserviceAttributes = { - certified: [ - [ - { - id: generateId(), - explicitAttributeVerification: false, - }, - ], - ], - declared: [], - verified: [], - }; - const descriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - attributes, - }; - // const eservice: EService = { - // ...mockEService, - // attributes, - // descriptors: [descriptor], - // }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - const updatedDescriptor = { - ...descriptor, - attributes, - }; - const updatedEService: EService = { - ...mockEService, - attributes: undefined, - descriptors: [updatedDescriptor], - }; - const payload: MovedAttributesFromEserviceToDescriptorsV1 = { - eservice: toEServiceV1(updatedEService), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "MovedAttributesFromEserviceToDescriptors", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceWithDescriptorsDeleted", async () => { + it.only("EServiceDescriptorUpdated (draft -> published)", async () => { const draftDescriptor: Descriptor = { ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), state: descriptorState.draft, }; const eservice: EService = { - ...mockEService, + ...getMockEService(), descriptors: [draftDescriptor], }; // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - const updatedEService: EService = { - ...eservice, - descriptors: [], + const publishedDescriptor: Descriptor = { + ...draftDescriptor, + publishedAt: new Date(), + state: descriptorState.published, }; - const payload: EServiceWithDescriptorsDeletedV1 = { - eservice: toEServiceV1(updatedEService), - descriptorId: draftDescriptor.id, + + const payload: EServiceDescriptorUpdatedV1 = { + eserviceId: eservice.id, + eserviceDescriptor: toDescriptorV1(publishedDescriptor), }; const message: EServiceEventEnvelope = { sequence_num: 1, - stream_id: mockEService.id, + stream_id: eservice.id, version: 2, - type: "EServiceWithDescriptorsDeleted", + type: "EServiceDescriptorUpdated", event_version: 1, data: payload, log_date: new Date(), }; await handleMessageV1(message, dynamoDBClient); + await new Promise((resolve) => setTimeout(resolve, 3000)); - // TO DO - expect(1).toBe(1); + const primaryKey = `ESERVICEDESCRIPTOR#${eservice.id}#${publishedDescriptor.id}`; + const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + const expectedEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: ItemState.Enum.ACTIVE, + descriptorAudience: publishedDescriptor.audience[0], + }; + expect(retrievedEntry).toEqual(expectedEntry); }); - it("EServiceDocumentUpdated", async () => { - const document = getMockDocument(); - const draftDescriptor: Descriptor = { + it("EServiceDescriptorUpdated (suspended -> published)", async () => { + const suspendedDescriptor: Descriptor = { ...getMockDescriptor(), - state: descriptorState.draft, - docs: [document], + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.suspended, + publishedAt: new Date(), + suspendedAt: new Date(), }; const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], + ...getMockEService(), + descriptors: [suspendedDescriptor], }; // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - const updatedDocument: Document = { - ...document, - prettyName: "updated pretty name", + const publishedDescriptor: Descriptor = { + ...suspendedDescriptor, + publishedAt: new Date(), + suspendedAt: new Date(), + state: descriptorState.published, }; - const payload: EServiceDocumentUpdatedV1 = { + const payload: EServiceDescriptorUpdatedV1 = { eserviceId: eservice.id, - descriptorId: draftDescriptor.id, - documentId: document.id, - serverUrls: [], - updatedDocument: toDocumentV1(updatedDocument), + eserviceDescriptor: toDescriptorV1(publishedDescriptor), }; const message: EServiceEventEnvelope = { sequence_num: 1, - stream_id: mockEService.id, + stream_id: eservice.id, version: 2, - type: "EServiceDocumentUpdated", + type: "EServiceDescriptorUpdated", event_version: 1, data: payload, log_date: new Date(), }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDeleted", async () => { - // await writeInReadmodel(toReadModelEService(mockEService), eservices, 1); - - const payload: EServiceDeletedV1 = { - eserviceId: mockEService.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDeleted", - event_version: 1, - data: payload, - log_date: new Date(), + const primaryKey = `ESERVICEDESCRIPTOR#${eservice.id}#${publishedDescriptor.id}`; + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: ItemState.Enum.INACTIVE, + descriptorAudience: publishedDescriptor.audience[0], }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); await handleMessageV1(message, dynamoDBClient); - // TO DO - expect(1).toBe(1); - }); - - describe("EServiceDocumentAdded", () => { - it("interface", async () => { - const descriptorInterface = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - docs: [], - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const payload: EServiceDocumentAddedV1 = { - eserviceId: eservice.id, - descriptorId: draftDescriptor.id, - serverUrls: ["pagopa.it"], - document: toDocumentV1(descriptorInterface), - isInterface: true, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDocumentAdded", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("document", async () => { - const document = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - docs: [], - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const payload: EServiceDocumentAddedV1 = { - eserviceId: eservice.id, - descriptorId: draftDescriptor.id, - serverUrls: [], - document: toDocumentV1(document), - isInterface: false, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDocumentAdded", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - }); - - describe("EServiceDocumentDeleted", () => { - it("interface", async () => { - const descriptorInterface = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - interface: descriptorInterface, - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const payload: EServiceDocumentDeletedV1 = { - eserviceId: eservice.id, - descriptorId: draftDescriptor.id, - documentId: descriptorInterface.id, - }; - - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDocumentDeleted", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("document", async () => { - const document = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - docs: [document], - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const payload: EServiceDocumentDeletedV1 = { - eserviceId: eservice.id, - descriptorId: draftDescriptor.id, - documentId: document.id, - }; - - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDocumentDeleted", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); + const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + const expectedEntry: PlatformStatesCatalogEntry = { + ...previousStateEntry, + state: ItemState.Enum.ACTIVE, + }; + expect(retrievedEntry).toEqual(expectedEntry); }); - it("EServiceDescriptorAdded", async () => { - const draftDescriptor: Descriptor = { + it("EServiceDescriptorUpdated (published -> suspended)", async () => { + const publishedDescriptor: Descriptor = { ...getMockDescriptor(), - state: descriptorState.draft, + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.published, + publishedAt: new Date(), }; const eservice: EService = { - ...mockEService, - descriptors: [], + ...getMockEService(), + descriptors: [publishedDescriptor], }; // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - const payload: EServiceDescriptorAddedV1 = { + const suspendedDescriptor: Descriptor = { + ...publishedDescriptor, + suspendedAt: new Date(), + state: descriptorState.suspended, + }; + + const payload: EServiceDescriptorUpdatedV1 = { eserviceId: eservice.id, - eserviceDescriptor: toDescriptorV1(draftDescriptor), + eserviceDescriptor: toDescriptorV1(suspendedDescriptor), }; const message: EServiceEventEnvelope = { sequence_num: 1, - stream_id: mockEService.id, + stream_id: eservice.id, version: 2, - type: "EServiceDescriptorAdded", + type: "EServiceDescriptorUpdated", event_version: 1, data: payload, log_date: new Date(), }; + const primaryKey = `ESERVICEDESCRIPTOR#${eservice.id}#${publishedDescriptor.id}`; + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: ItemState.Enum.ACTIVE, + descriptorAudience: publishedDescriptor.audience[0], + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); await handleMessageV1(message, dynamoDBClient); - // TO DO - expect(1).toBe(1); + const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + const expectedEntry: PlatformStatesCatalogEntry = { + ...previousStateEntry, + state: ItemState.Enum.INACTIVE, + }; + expect(retrievedEntry).toEqual(expectedEntry); }); - it("EServiceDescriptorUpdated", async () => { - const draftDescriptor: Descriptor = { + // descriptorState.published for archiving + it("EServiceDescriptorUpdated (published -> archived)", async () => { + const publishedDescriptor: Descriptor = { ...getMockDescriptor(), + audience: ["pagopa.it"], interface: getMockDocument(), - state: descriptorState.draft, + state: descriptorState.published, + publishedAt: new Date(), }; const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], + ...getMockEService(), + descriptors: [publishedDescriptor], }; // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - const publishedDescriptor: Descriptor = { - ...draftDescriptor, - publishedAt: new Date(), - state: descriptorState.published, + const archivedDescriptor: Descriptor = { + ...publishedDescriptor, + archivedAt: new Date(), + state: descriptorState.archived, }; const payload: EServiceDescriptorUpdatedV1 = { eserviceId: eservice.id, - eserviceDescriptor: toDescriptorV1(publishedDescriptor), + eserviceDescriptor: toDescriptorV1(archivedDescriptor), }; const message: EServiceEventEnvelope = { sequence_num: 1, - stream_id: mockEService.id, + stream_id: eservice.id, version: 2, type: "EServiceDescriptorUpdated", event_version: 1, data: payload, log_date: new Date(), }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceRiskAnalysisDeleted", async () => { - const riskAnalysis = getMockValidRiskAnalysis("PA"); - // const eservice: EService = { - // ...mockEService, - // riskAnalysis: [riskAnalysis], - // }; - - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedEService: EService = { - ...mockEService, - riskAnalysis: [], - }; - const payload: EServiceRiskAnalysisDeletedV1 = { - eservice: toEServiceV1(updatedEService), - riskAnalysisId: riskAnalysis.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceRiskAnalysisDeleted", - event_version: 1, - data: payload, - log_date: new Date(), + const primaryKey = `ESERVICEDESCRIPTOR#${eservice.id}#${publishedDescriptor.id}`; + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: ItemState.Enum.INACTIVE, + descriptorAudience: publishedDescriptor.audience[0], }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); await handleMessageV1(message, dynamoDBClient); - // TO DO - expect(1).toBe(1); + const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + console.log(retrievedEntry); + expect(retrievedEntry).toBeUndefined(); }); }); - describe("Events V2", async () => { + describe.skip("Events V2", async () => { const mockEService = getMockEService(); it("EServiceDescriptorActivated", async () => { const suspendedDescriptor: Descriptor = { From 1c1a60e30d550fdf610d91249491d03d4b66f668 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 28 Aug 2024 16:17:39 +0200 Subject: [PATCH 024/241] Refactor --- ...logPlatformstateWriter.integration.test.ts | 44 ++++++++++++------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index dd2cbaa3c4..4403838700 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -13,7 +13,13 @@ import { descriptorState, toEServiceV2, } from "pagopa-interop-models"; -import * as dynamodb from "@aws-sdk/client-dynamodb"; +import { + CreateTableCommand, + CreateTableInput, + DeleteTableCommand, + DeleteTableInput, + DynamoDBClient, +} from "@aws-sdk/client-dynamodb"; import { toDescriptorV1, getMockDescriptor, @@ -27,7 +33,7 @@ import { handleMessageV2 } from "../src/consumerServiceV2.js"; import { config } from "./utils.js"; describe("database test", async () => { - const dynamoDBClient = new dynamodb.DynamoDB({ + const dynamoDBClient = new DynamoDBClient({ credentials: { accessKeyId: "key", secretAccessKey: "secret" }, region: "eu-central-1", // eslint-disable-next-line @typescript-eslint/no-non-null-assertion @@ -37,40 +43,42 @@ describe("database test", async () => { }`, }); beforeEach(async () => { - const platformTableDefinition: dynamodb.CreateTableInput = { + const platformTableDefinition: CreateTableInput = { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion TableName: config!.tokenGenerationReadModelTableNamePlatform, AttributeDefinitions: [{ AttributeName: "PK", AttributeType: "S" }], KeySchema: [{ AttributeName: "PK", KeyType: "HASH" }], BillingMode: "PAY_PER_REQUEST", }; - await dynamoDBClient.createTable(platformTableDefinition); - const tokenGenerationTableDefinition: dynamodb.CreateTableInput = { + const command1 = new CreateTableCommand(platformTableDefinition); + await dynamoDBClient.send(command1); + + const tokenGenerationTableDefinition: CreateTableInput = { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion TableName: config!.tokenGenerationReadModelTableNameTokenGeneration, AttributeDefinitions: [{ AttributeName: "PK", AttributeType: "S" }], KeySchema: [{ AttributeName: "PK", KeyType: "HASH" }], BillingMode: "PAY_PER_REQUEST", }; - await dynamoDBClient.createTable(tokenGenerationTableDefinition); - - // const tablesResult = await dynamoDBClient.listTables(); - // console.log(tablesResult.TableNames); + const command2 = new CreateTableCommand(tokenGenerationTableDefinition); + await dynamoDBClient.send(command2); }); afterEach(async () => { - const tableToDelete1: dynamodb.DeleteTableInput = { + const tableToDelete1: DeleteTableInput = { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion TableName: config!.tokenGenerationReadModelTableNamePlatform, }; - const tableToDelete2: dynamodb.DeleteTableInput = { + const tableToDelete2: DeleteTableInput = { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion TableName: config!.tokenGenerationReadModelTableNameTokenGeneration, }; - await dynamoDBClient.deleteTable(tableToDelete1); - await dynamoDBClient.deleteTable(tableToDelete2); + const command1 = new DeleteTableCommand(tableToDelete1); + await dynamoDBClient.send(command1); + const command2 = new DeleteTableCommand(tableToDelete2); + await dynamoDBClient.send(command2); }); describe("Events V1", async () => { - it.only("EServiceDescriptorUpdated (draft -> published)", async () => { + it("EServiceDescriptorUpdated (draft -> published)", async () => { const draftDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], @@ -103,7 +111,7 @@ describe("database test", async () => { log_date: new Date(), }; await handleMessageV1(message, dynamoDBClient); - await new Promise((resolve) => setTimeout(resolve, 3000)); + await new Promise((resolve) => setTimeout(resolve, 1000)); const primaryKey = `ESERVICEDESCRIPTOR#${eservice.id}#${publishedDescriptor.id}`; const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); @@ -158,6 +166,7 @@ describe("database test", async () => { }; await writeCatalogEntry(previousStateEntry, dynamoDBClient); await handleMessageV1(message, dynamoDBClient); + await new Promise((resolve) => setTimeout(resolve, 1000)); const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); const expectedEntry: PlatformStatesCatalogEntry = { @@ -208,6 +217,7 @@ describe("database test", async () => { }; await writeCatalogEntry(previousStateEntry, dynamoDBClient); await handleMessageV1(message, dynamoDBClient); + await new Promise((resolve) => setTimeout(resolve, 1000)); const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); const expectedEntry: PlatformStatesCatalogEntry = { @@ -259,14 +269,14 @@ describe("database test", async () => { }; await writeCatalogEntry(previousStateEntry, dynamoDBClient); await handleMessageV1(message, dynamoDBClient); + await new Promise((resolve) => setTimeout(resolve, 1000)); const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); - console.log(retrievedEntry); expect(retrievedEntry).toBeUndefined(); }); }); - describe.skip("Events V2", async () => { + describe("Events V2", async () => { const mockEService = getMockEService(); it("EServiceDescriptorActivated", async () => { const suspendedDescriptor: Descriptor = { From f3631477e99264b6d1ea7c2b780dd9404906bda5 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 28 Aug 2024 16:22:28 +0200 Subject: [PATCH 025/241] Refactor --- ...logPlatformstateWriter.integration.test.ts | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 8f053f29c3..59eaacad7c 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -1,5 +1,5 @@ import { format } from "util"; -import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { AttributeId, ClonedEServiceAddedV1, @@ -30,7 +30,13 @@ import { generateId, toEServiceV2, } from "pagopa-interop-models"; -import * as dynamodb from "@aws-sdk/client-dynamodb"; +import { + CreateTableCommand, + CreateTableInput, + DeleteTableCommand, + DeleteTableInput, + DynamoDBClient, +} from "@aws-sdk/client-dynamodb"; import { toEServiceV1, getMockValidRiskAnalysis, @@ -47,7 +53,7 @@ import { handleMessageV2 } from "../src/consumerServiceV2.js"; import { config } from "./utils.js"; describe("database test", async () => { - const dynamoDBClient = new dynamodb.DynamoDB({ + const dynamoDBClient = new DynamoDBClient({ credentials: { accessKeyId: "key", secretAccessKey: "secret" }, region: "eu-central-1", // eslint-disable-next-line @typescript-eslint/no-non-null-assertion @@ -56,39 +62,43 @@ describe("database test", async () => { config!.tokenGenerationReadModelDbPort }`, }); - beforeAll(async () => { - const platformTableDefinition: dynamodb.CreateTableInput = { + beforeEach(async () => { + const platformTableDefinition: CreateTableInput = { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion TableName: config!.tokenGenerationReadModelTableNamePlatform, AttributeDefinitions: [{ AttributeName: "PK", AttributeType: "S" }], KeySchema: [{ AttributeName: "PK", KeyType: "HASH" }], BillingMode: "PAY_PER_REQUEST", }; - await dynamoDBClient.createTable(platformTableDefinition); + const command1 = new CreateTableCommand(platformTableDefinition); + await dynamoDBClient.send(command1); - const tokenGenerationTableDefinition: dynamodb.CreateTableInput = { + const tokenGenerationTableDefinition: CreateTableInput = { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion TableName: config!.tokenGenerationReadModelTableNameTokenGeneration, AttributeDefinitions: [{ AttributeName: "PK", AttributeType: "S" }], KeySchema: [{ AttributeName: "PK", KeyType: "HASH" }], BillingMode: "PAY_PER_REQUEST", }; - await dynamoDBClient.createTable(tokenGenerationTableDefinition); + const command2 = new CreateTableCommand(tokenGenerationTableDefinition); + await dynamoDBClient.send(command2); // const tablesResult = await dynamoDBClient.listTables(); // console.log(tablesResult.TableNames); }); - afterAll(async () => { - const tableToDelete1: dynamodb.DeleteTableInput = { + afterEach(async () => { + const tableToDelete1: DeleteTableInput = { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion TableName: config!.tokenGenerationReadModelTableNamePlatform, }; - const tableToDelete2: dynamodb.DeleteTableInput = { + const tableToDelete2: DeleteTableInput = { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion TableName: config!.tokenGenerationReadModelTableNameTokenGeneration, }; - await dynamoDBClient.deleteTable(tableToDelete1); - await dynamoDBClient.deleteTable(tableToDelete2); + const command1 = new DeleteTableCommand(tableToDelete1); + await dynamoDBClient.send(command1); + const command2 = new DeleteTableCommand(tableToDelete2); + await dynamoDBClient.send(command2); }); describe("Events V1", async () => { const mockEService = getMockEService(); From bb7690e2ebee9469c2af62ec184542b9e8d17128 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 28 Aug 2024 16:41:56 +0200 Subject: [PATCH 026/241] Update models --- packages/catalog-platformstate-writer/src/utils.ts | 6 ++++++ .../src/token-generation-readmodel/platform-states-entry.ts | 4 +++- .../token-generation-states-entry.ts | 4 +++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index d52303aa0d..22f3d4c27f 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -33,6 +33,12 @@ export const writeCatalogEntry = async ( descriptorAudience: { S: catalogEntry.descriptorAudience, }, + version: { + N: catalogEntry.version.toString(), + }, + updatedAt: { + S: catalogEntry.updatedAt, + }, }, TableName: config.tokenGenerationReadModelTableNamePlatform, }; diff --git a/packages/models/src/token-generation-readmodel/platform-states-entry.ts b/packages/models/src/token-generation-readmodel/platform-states-entry.ts index bbdc6d106f..ecd7eedeaa 100644 --- a/packages/models/src/token-generation-readmodel/platform-states-entry.ts +++ b/packages/models/src/token-generation-readmodel/platform-states-entry.ts @@ -27,6 +27,8 @@ type PrimaryKeyPlatformStates = const PlatformStatesBaseEntry = z.object({ PK: z.string(), state: ItemState, + version: z.number(), + updatedAt: z.string().datetime(), }); type PlatformStatesBaseEntry = z.infer; @@ -48,7 +50,7 @@ export type PlatformStatesPurposeEntry = z.infer< export const PlatformStatesAgreementEntry = PlatformStatesBaseEntry.extend({ GSIPK_consumerId_eserviceId: z.string(), - GSISK_agreementTimestamp: z.string(), + GSISK_agreementTimestamp: z.string().datetime(), agreementDescriptorId: z.string(), }); export type PlatformStatesAgreementEntry = z.infer< diff --git a/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts b/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts index 55444c51b5..e914ccfdd6 100644 --- a/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts +++ b/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts @@ -1,6 +1,6 @@ import { z } from "zod"; import { ClientKind } from "../authorization/client.js"; -import { PurposeVersionId } from "../brandedIds.js"; +import { AgreementId, PurposeVersionId } from "../brandedIds.js"; import { ItemState } from "./platform-states-entry.js"; const TokenGenerationStatesBaseEntry = z.object({ @@ -11,6 +11,7 @@ const TokenGenerationStatesBaseEntry = z.object({ GSIPK_clientId: z.string(), GSIPK_kid: z.string(), GSIPK_clientId_purposeId: z.string(), + updatedAt: z.string().datetime(), }); type TokenGenerationStatesBaseEntry = z.infer< typeof TokenGenerationStatesBaseEntry @@ -20,6 +21,7 @@ export const TokenGenerationStatesClientPurposeEntry = TokenGenerationStatesBaseEntry && z.object({ GSIPK_consumerId_eserviceId: z.string(), + agreementId: AgreementId, agreementState: ItemState, GSIPK_eserviceId_descriptorId: z.string(), descriptorState: ItemState, From 05b13edcb00ebfe2ea45d8eb8dc720e0929160fa Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 28 Aug 2024 16:57:18 +0200 Subject: [PATCH 027/241] Adjust logic and tests --- .../src/consumerServiceV2.ts | 4 +++ ...logPlatformstateWriter.integration.test.ts | 30 ++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index a28e39c2a6..ab8b685645 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -45,6 +45,8 @@ export async function handleMessageV2( PK: `ESERVICEDESCRIPTOR#${eservice.id}#${descriptorId}`, state: descriptorStateToClientState(descriptorState), descriptorAudience: descriptor.audience[0], + version: msg.version, + updatedAt: new Date().toISOString(), }; await writeCatalogEntry(catalogEntry, dynamoDBClient); @@ -78,6 +80,8 @@ export async function handleMessageV2( msg.type === "EServiceDescriptorActivated" ? ItemState.Enum.ACTIVE : ItemState.Enum.INACTIVE, + version: msg.version, + updatedAt: new Date().toISOString(), }; await writeCatalogEntry(updatedCatalogEntry, dynamoDBClient); } diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 59eaacad7c..0aa6fc5e21 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -1,5 +1,14 @@ import { format } from "util"; -import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, + vi, +} from "vitest"; import { AttributeId, ClonedEServiceAddedV1, @@ -100,6 +109,13 @@ describe("database test", async () => { const command2 = new DeleteTableCommand(tableToDelete2); await dynamoDBClient.send(command2); }); + beforeAll(() => { + vi.useFakeTimers(); + vi.setSystemTime(new Date()); + }); + afterAll(() => { + vi.useRealTimers(); + }); describe("Events V1", async () => { const mockEService = getMockEService(); it("EServiceAdded", async () => { @@ -650,6 +666,8 @@ describe("database test", async () => { PK: primaryKey, state: ItemState.Enum.INACTIVE, descriptorAudience: publishedDescriptor.audience[0], + version: 1, + updatedAt: new Date().toISOString(), }; await writeCatalogEntry(previousStateEntry, dynamoDBClient); await handleMessageV2(message, dynamoDBClient); @@ -658,6 +676,8 @@ describe("database test", async () => { const expectedEntry: PlatformStatesCatalogEntry = { ...previousStateEntry, state: ItemState.Enum.ACTIVE, + version: 2, + updatedAt: new Date().toISOString(), }; expect(retrievedEntry).toEqual(expectedEntry); }); @@ -703,6 +723,8 @@ describe("database test", async () => { PK: primaryKey, state: ItemState.Enum.INACTIVE, descriptorAudience: publishedDescriptor.audience[0], + version: 1, + updatedAt: new Date().toISOString(), }; await writeCatalogEntry(previousStateEntry, dynamoDBClient); await handleMessageV2(message, dynamoDBClient); @@ -754,6 +776,8 @@ describe("database test", async () => { PK: primaryKey, state: ItemState.Enum.ACTIVE, descriptorAudience: publishedDescriptor.audience[0], + version: 2, + updatedAt: new Date().toISOString(), }; expect(retrievedEntry).toEqual(expectedEntry); }); @@ -799,6 +823,8 @@ describe("database test", async () => { PK: primaryKey, state: ItemState.Enum.ACTIVE, descriptorAudience: publishedDescriptor.audience[0], + version: 1, + updatedAt: new Date().toISOString(), }; await writeCatalogEntry(previousStateEntry, dynamoDBClient); await handleMessageV2(message, dynamoDBClient); @@ -807,6 +833,8 @@ describe("database test", async () => { const expectedEntry: PlatformStatesCatalogEntry = { ...previousStateEntry, state: ItemState.Enum.INACTIVE, + version: 2, + updatedAt: new Date().toISOString(), }; expect(retrievedEntry).toEqual(expectedEntry); }); From 33479df57689711f0c73e93a0ab84b09b8d99cce Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 28 Aug 2024 16:57:55 +0200 Subject: [PATCH 028/241] Remove v1 tests from this branch --- ...logPlatformstateWriter.integration.test.ts | 527 ------------------ 1 file changed, 527 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 0aa6fc5e21..426c4f68fd 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -1,4 +1,3 @@ -import { format } from "util"; import { afterAll, afterEach, @@ -10,33 +9,16 @@ import { vi, } from "vitest"; import { - AttributeId, - ClonedEServiceAddedV1, Descriptor, - Document, EService, - EServiceAddedV1, - EServiceDeletedV1, EServiceDescriptorActivatedV2, - EServiceDescriptorAddedV1, EServiceDescriptorArchivedV2, EServiceDescriptorPublishedV2, EServiceDescriptorSuspendedV2, - EServiceDescriptorUpdatedV1, - EServiceDocumentAddedV1, - EServiceDocumentDeletedV1, - EServiceDocumentUpdatedV1, EServiceEventEnvelope, - EServiceRiskAnalysisAddedV1, - EServiceRiskAnalysisDeletedV1, - EServiceUpdatedV1, - EServiceWithDescriptorsDeletedV1, - EserviceAttributes, ItemState, - MovedAttributesFromEserviceToDescriptorsV1, PlatformStatesCatalogEntry, descriptorState, - generateId, toEServiceV2, } from "pagopa-interop-models"; import { @@ -47,16 +29,11 @@ import { DynamoDBClient, } from "@aws-sdk/client-dynamodb"; import { - toEServiceV1, - getMockValidRiskAnalysis, - toDocumentV1, - toDescriptorV1, getMockDescriptor, getMockEService, getMockDocument, } from "pagopa-interop-commons-test"; import { readCatalogEntry, writeCatalogEntry } from "../src/utils.js"; -import { handleMessageV1 } from "../src/consumerServiceV1.js"; import { handleMessageV2 } from "../src/consumerServiceV2.js"; import { config } from "./utils.js"; @@ -116,510 +93,6 @@ describe("database test", async () => { afterAll(() => { vi.useRealTimers(); }); - describe("Events V1", async () => { - const mockEService = getMockEService(); - it("EServiceAdded", async () => { - const payload: EServiceAddedV1 = { - eservice: toEServiceV1(mockEService), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 1, - type: "EServiceAdded", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("ClonedEServiceAdded", async () => { - // await writeInReadmodel( - // toReadModelEService(mockEService), - // eservices, - // 1 - // ); - - const date = new Date(); - const clonedEService: EService = { - ...mockEService, - id: generateId(), - createdAt: new Date(), - name: `${mockEService.name} - clone - ${format( - date, - "dd/MM/yyyy HH:mm:ss" - )}`, - }; - - const payload: ClonedEServiceAddedV1 = { - eservice: toEServiceV1(clonedEService), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: clonedEService.id, - version: 1, - type: "ClonedEServiceAdded", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceUpdated", async () => { - // await writeInReadmodel( - // toReadModelEService(mockEService), - // eservices, - // 1 - // ); - - const updatedEService: EService = { - ...mockEService, - description: "updated description", - }; - const payload: EServiceUpdatedV1 = { - eservice: toEServiceV1(updatedEService), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceUpdated", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceRiskAnalysisAdded", async () => { - // await writeInReadmodel( - // toReadModelEService(mockEService), - // eservices, - // 1 - // ); - - const mockRiskAnalysis = getMockValidRiskAnalysis("PA"); - const updatedEService: EService = { - ...mockEService, - riskAnalysis: [...mockEService.riskAnalysis, mockRiskAnalysis], - }; - const payload: EServiceRiskAnalysisAddedV1 = { - eservice: toEServiceV1(updatedEService), - riskAnalysisId: mockRiskAnalysis.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceRiskAnalysisAdded", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("MovedAttributesFromEserviceToDescriptors", async () => { - const attributes: EserviceAttributes = { - certified: [ - [ - { - id: generateId(), - explicitAttributeVerification: false, - }, - ], - ], - declared: [], - verified: [], - }; - const descriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - attributes, - }; - // const eservice: EService = { - // ...mockEService, - // attributes, - // descriptors: [descriptor], - // }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - const updatedDescriptor = { - ...descriptor, - attributes, - }; - const updatedEService: EService = { - ...mockEService, - attributes: undefined, - descriptors: [updatedDescriptor], - }; - const payload: MovedAttributesFromEserviceToDescriptorsV1 = { - eservice: toEServiceV1(updatedEService), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "MovedAttributesFromEserviceToDescriptors", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceWithDescriptorsDeleted", async () => { - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedEService: EService = { - ...eservice, - descriptors: [], - }; - const payload: EServiceWithDescriptorsDeletedV1 = { - eservice: toEServiceV1(updatedEService), - descriptorId: draftDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceWithDescriptorsDeleted", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDocumentUpdated", async () => { - const document = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - docs: [document], - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedDocument: Document = { - ...document, - prettyName: "updated pretty name", - }; - - const payload: EServiceDocumentUpdatedV1 = { - eserviceId: eservice.id, - descriptorId: draftDescriptor.id, - documentId: document.id, - serverUrls: [], - updatedDocument: toDocumentV1(updatedDocument), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDocumentUpdated", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDeleted", async () => { - // await writeInReadmodel(toReadModelEService(mockEService), eservices, 1); - - const payload: EServiceDeletedV1 = { - eserviceId: mockEService.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDeleted", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - describe("EServiceDocumentAdded", () => { - it("interface", async () => { - const descriptorInterface = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - docs: [], - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const payload: EServiceDocumentAddedV1 = { - eserviceId: eservice.id, - descriptorId: draftDescriptor.id, - serverUrls: ["pagopa.it"], - document: toDocumentV1(descriptorInterface), - isInterface: true, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDocumentAdded", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("document", async () => { - const document = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - docs: [], - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const payload: EServiceDocumentAddedV1 = { - eserviceId: eservice.id, - descriptorId: draftDescriptor.id, - serverUrls: [], - document: toDocumentV1(document), - isInterface: false, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDocumentAdded", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - }); - - describe("EServiceDocumentDeleted", () => { - it("interface", async () => { - const descriptorInterface = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - interface: descriptorInterface, - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const payload: EServiceDocumentDeletedV1 = { - eserviceId: eservice.id, - descriptorId: draftDescriptor.id, - documentId: descriptorInterface.id, - }; - - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDocumentDeleted", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("document", async () => { - const document = getMockDocument(); - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - docs: [document], - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const payload: EServiceDocumentDeletedV1 = { - eserviceId: eservice.id, - descriptorId: draftDescriptor.id, - documentId: document.id, - }; - - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDocumentDeleted", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - }); - - it("EServiceDescriptorAdded", async () => { - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.draft, - }; - const eservice: EService = { - ...mockEService, - descriptors: [], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const payload: EServiceDescriptorAddedV1 = { - eserviceId: eservice.id, - eserviceDescriptor: toDescriptorV1(draftDescriptor), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorAdded", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceDescriptorUpdated", async () => { - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - interface: getMockDocument(), - state: descriptorState.draft, - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const publishedDescriptor: Descriptor = { - ...draftDescriptor, - publishedAt: new Date(), - state: descriptorState.published, - }; - - const payload: EServiceDescriptorUpdatedV1 = { - eserviceId: eservice.id, - eserviceDescriptor: toDescriptorV1(publishedDescriptor), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorUpdated", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - - it("EServiceRiskAnalysisDeleted", async () => { - const riskAnalysis = getMockValidRiskAnalysis("PA"); - // const eservice: EService = { - // ...mockEService, - // riskAnalysis: [riskAnalysis], - // }; - - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const updatedEService: EService = { - ...mockEService, - riskAnalysis: [], - }; - const payload: EServiceRiskAnalysisDeletedV1 = { - eservice: toEServiceV1(updatedEService), - riskAnalysisId: riskAnalysis.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceRiskAnalysisDeleted", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - - // TO DO - expect(1).toBe(1); - }); - }); describe("Events V2", async () => { const mockEService = getMockEService(); From ffcd8ab7d7bab0cc46536a95dd32cc764f434f46 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 28 Aug 2024 17:03:07 +0200 Subject: [PATCH 029/241] Remove console log --- .../catalog-platformstate-writer/src/consumerServiceV1.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index fbccfe0001..38d4acd734 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -36,14 +36,14 @@ export async function handleMessageV1( descriptorState.published, descriptorState.suspended, async () => { - console.log(descriptor.state); const catalogEntry: PlatformStatesCatalogEntry = { // TODO: change with the PK type PK: `ESERVICEDESCRIPTOR#${eserviceId}#${descriptor.id}`, state: descriptorStateToClientState(descriptor.state), descriptorAudience: descriptor.audience[0], + version: msg.version, + updatedAt: new Date().toISOString(), }; - console.log(catalogEntry); await writeCatalogEntry(catalogEntry, dynamoDBClient); } ) From 1896362b48f67ef18d9f330c8400b8eec1c66898 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 28 Aug 2024 17:06:48 +0200 Subject: [PATCH 030/241] Remove sample test --- packages/catalog-platformstate-writer/test/sample.test.ts | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 packages/catalog-platformstate-writer/test/sample.test.ts diff --git a/packages/catalog-platformstate-writer/test/sample.test.ts b/packages/catalog-platformstate-writer/test/sample.test.ts deleted file mode 100644 index a0769ee33d..0000000000 --- a/packages/catalog-platformstate-writer/test/sample.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { describe, expect, it } from "vitest"; - -describe(() => { - it("sample", () => { - expect(1).toBe(1); - }); -}); From a56a2b2b5caddd06826acacb5b55a15e9cb682a0 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Thu, 29 Aug 2024 10:53:07 +0200 Subject: [PATCH 031/241] Fix sleep with fake timers --- .../catalog-platformstate-writer/src/utils.ts | 9 +++++++++ ...catalogPlatformstateWriter.integration.test.ts | 15 +++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index 736703dc89..634ca284c5 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -19,6 +19,7 @@ import { ScanInput, } from "@aws-sdk/client-dynamodb"; import { unmarshall } from "@aws-sdk/util-dynamodb"; +import { vi } from "vitest"; import { config } from "./config/config.js"; export const writeCatalogEntry = async ( @@ -110,3 +111,11 @@ export const descriptorStateToClientState = ( state === descriptorState.published || state === descriptorState.deprecated ? ItemState.Enum.ACTIVE : ItemState.Enum.INACTIVE; + +export const sleep = (ms: number, mockDate = new Date()): Promise => + new Promise((resolve) => { + vi.useRealTimers(); + setTimeout(resolve, ms); + vi.useFakeTimers(); + vi.setSystemTime(mockDate); + }); diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 7c91c2d087..0844e0cc31 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -35,7 +35,7 @@ import { getMockEService, getMockDocument, } from "pagopa-interop-commons-test"; -import { readCatalogEntry, writeCatalogEntry } from "../src/utils.js"; +import { readCatalogEntry, sleep, writeCatalogEntry } from "../src/utils.js"; import { handleMessageV1 } from "../src/consumerServiceV1.js"; import { handleMessageV2 } from "../src/consumerServiceV2.js"; @@ -86,9 +86,10 @@ describe("database test", async () => { const command2 = new DeleteTableCommand(tableToDelete2); await dynamoDBClient.send(command2); }); + const mockDate = new Date(); beforeAll(() => { vi.useFakeTimers(); - vi.setSystemTime(new Date()); + vi.setSystemTime(mockDate); }); afterAll(() => { vi.useRealTimers(); @@ -128,7 +129,7 @@ describe("database test", async () => { log_date: new Date(), }; await handleMessageV1(message, dynamoDBClient); - await new Promise((resolve) => setTimeout(resolve, 1000)); + await sleep(1000, mockDate); const primaryKey = `ESERVICEDESCRIPTOR#${eservice.id}#${publishedDescriptor.id}`; const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); @@ -187,12 +188,13 @@ describe("database test", async () => { }; await writeCatalogEntry(previousStateEntry, dynamoDBClient); await handleMessageV1(message, dynamoDBClient); - await new Promise((resolve) => setTimeout(resolve, 1000)); + await sleep(1000, mockDate); const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); const expectedEntry: PlatformStatesCatalogEntry = { ...previousStateEntry, state: ItemState.Enum.ACTIVE, + version: 2, }; expect(retrievedEntry).toEqual(expectedEntry); }); @@ -240,12 +242,13 @@ describe("database test", async () => { }; await writeCatalogEntry(previousStateEntry, dynamoDBClient); await handleMessageV1(message, dynamoDBClient); - await new Promise((resolve) => setTimeout(resolve, 1000)); + await sleep(1000, mockDate); const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); const expectedEntry: PlatformStatesCatalogEntry = { ...previousStateEntry, state: ItemState.Enum.INACTIVE, + version: 2, }; expect(retrievedEntry).toEqual(expectedEntry); }); @@ -294,7 +297,7 @@ describe("database test", async () => { }; await writeCatalogEntry(previousStateEntry, dynamoDBClient); await handleMessageV1(message, dynamoDBClient); - await new Promise((resolve) => setTimeout(resolve, 1000)); + await sleep(1000, mockDate); const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); expect(retrievedEntry).toBeUndefined(); From 6d041467b2ed53b4b83086a350fefafbeee9b5a4 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Thu, 29 Aug 2024 14:51:52 +0200 Subject: [PATCH 032/241] WIP --- .../src/consumerServiceV2.ts | 32 +++- .../catalog-platformstate-writer/src/utils.ts | 159 ++++++++++++++++++ ...logPlatformstateWriter.integration.test.ts | 102 +++++++++-- .../token-generation-states-entry.ts | 17 +- 4 files changed, 289 insertions(+), 21 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index ab8b685645..8318cc37fc 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -3,6 +3,7 @@ import { DescriptorState, EServiceEventEnvelopeV2, fromEServiceDescriptorStateV2, + fromEServiceV2, genericInternalError, ItemState, PlatformStatesCatalogEntry, @@ -12,6 +13,8 @@ import { deleteCatalogEntry, descriptorStateToClientState, readCatalogEntry, + readTokenStateEntryByEserviceIdAndDescriptorId, + updateDescriptorState, writeCatalogEntry, } from "./utils.js"; @@ -57,15 +60,17 @@ export async function handleMessageV2( { type: "EServiceDescriptorActivated" }, { type: "EServiceDescriptorSuspended" }, async (msg) => { - const eservice = msg.data.eservice; - if (!eservice) { + const eserviceV2 = msg.data.eservice; + if (!eserviceV2) { throw genericInternalError( `EService not found in message data for event ${msg.type}` ); } + const eservice = fromEServiceV2(eserviceV2); + const descriptorId = msg.data.descriptorId; // TODO: change with the PK type - const primaryKey = `ESERVICEDESCRIPTOR#${eservice.id}#${msg.data.descriptorId}`; + const primaryKey = `ESERVICEDESCRIPTOR#${eservice.id}#${descriptorId}`; // TODO: remove read? const catalogEntry = await readCatalogEntry(primaryKey, dynamoDBClient); @@ -84,9 +89,26 @@ export async function handleMessageV2( updatedAt: new Date().toISOString(), }; await writeCatalogEntry(updatedCatalogEntry, dynamoDBClient); - } - // TODO: Add token-generation-states part + // token-generation-states + const eserviceId_descriptorId = `${eservice.id}#${descriptorId}`; + const result = await readTokenStateEntryByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + + if (result) { + await updateDescriptorState( + dynamoDBClient, + result.PK, + updatedCatalogEntry.state + ); + } else { + throw genericInternalError( + `Unable to find token generation state entry with GSIPK_eserviceId_descriptorId ${eserviceId_descriptorId}` + ); + } + } } ) .with({ type: "EServiceDescriptorArchived" }, async (msg) => { diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index 736703dc89..371c242a5f 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -1,9 +1,11 @@ +import { vi } from "vitest"; import { descriptorState, DescriptorState, genericInternalError, ItemState, PlatformStatesCatalogEntry, + TokenGenerationStatesClientPurposeEntry, } from "pagopa-interop-models"; import { DeleteItemCommand, @@ -14,9 +16,14 @@ import { GetItemInput, PutItemCommand, PutItemInput, + QueryCommand, + QueryCommandOutput, + QueryInput, ScanCommand, ScanCommandOutput, ScanInput, + UpdateItemCommand, + UpdateItemInput, } from "@aws-sdk/client-dynamodb"; import { unmarshall } from "@aws-sdk/util-dynamodb"; import { config } from "./config/config.js"; @@ -110,3 +117,155 @@ export const descriptorStateToClientState = ( state === descriptorState.published || state === descriptorState.deprecated ? ItemState.Enum.ACTIVE : ItemState.Enum.INACTIVE; + +export const updateDescriptorState = async ( + dynamoDBClient: DynamoDBClient, + primaryKey: string, + state: ItemState +): Promise => { + const input: UpdateItemInput = { + Key: { + PK: { + S: primaryKey, + }, + }, + ExpressionAttributeValues: { + ":newState": { + S: state, + }, + ":newUpdateAt": { + S: new Date().toISOString(), + }, + }, + UpdateExpression: + "SET descriptorState = :newState, updatedAt = :newUpdateAt", + TableName: config.tokenGenerationReadModelTableNameTokenGeneration, + ReturnValues: "ALL_NEW", + }; + const command = new UpdateItemCommand(input); + await dynamoDBClient.send(command); +}; + +export const writeTokenStateEntry = async ( + tokenStateEntry: TokenGenerationStatesClientPurposeEntry, + dynamoDBClient: DynamoDBClient +): Promise => { + const input: PutItemInput = { + Item: { + PK: { + S: tokenStateEntry.PK, + }, + descriptorState: { + S: tokenStateEntry.descriptorState, + }, + descriptorAudience: { + S: tokenStateEntry.descriptorAudience, + }, + updatedAt: { + S: tokenStateEntry.updatedAt, + }, + consumerId: { + S: tokenStateEntry.consumerId, + }, + agreementId: { + S: tokenStateEntry.agreementId, + }, + purposeVersionId: { + S: tokenStateEntry.purposeVersionId, + }, + GSIPK_consumerId_eserviceId: { + S: tokenStateEntry.GSIPK_consumerId_eserviceId, + }, + clientKind: { + S: tokenStateEntry.clientKind, + }, + publicKey: { + S: tokenStateEntry.publicKey, + }, + GSIPK_clientId: { + S: tokenStateEntry.GSIPK_clientId, + }, + GSIPK_kid: { + S: tokenStateEntry.GSIPK_kid, + }, + GSIPK_clientId_purposeId: { + S: tokenStateEntry.GSIPK_clientId_purposeId, + }, + agreementState: { + S: tokenStateEntry.agreementState, + }, + GSIPK_eserviceId_descriptorId: { + S: tokenStateEntry.GSIPK_eserviceId_descriptorId, + }, + GSIPK_purposeId: { + S: tokenStateEntry.GSIPK_purposeId, + }, + purposeState: { + S: tokenStateEntry.purposeState, + }, + }, + TableName: config.tokenGenerationReadModelTableNameTokenGeneration, + }; + const command = new PutItemCommand(input); + console.log( + "tokenStateEntry.GSIPK_eserviceId_descriptorId ", + tokenStateEntry.GSIPK_eserviceId_descriptorId + ); + console.log("write token state", await dynamoDBClient.send(command)); +}; + +export const readTokenStateEntryByEserviceIdAndDescriptorId = async ( + eserviceId_descriptorId: string, + dynamoDBClient: DynamoDBClient +): Promise => { + console.log("eserviceId_descriptorId ", eserviceId_descriptorId); + const input: QueryInput = { + TableName: config.tokenGenerationReadModelTableNameTokenGeneration, + IndexName: "gsiIndex", // Use the name of your Global Secondary Index + KeyConditionExpression: `GSIPK_eserviceId_descriptorId = :gsi_value`, + ExpressionAttributeValues: { + ":gsi_value": { S: eserviceId_descriptorId }, + }, + // ExpressionAttributeNames: { + // "#gsi": "GSIPK_eserviceId_descriptorId", + // }, + ScanIndexForward: false, + }; + const command = new QueryCommand(input); + const data: QueryCommandOutput = await dynamoDBClient.send(command); + console.log("data.Items ", data); + + // const input: GetItemInput = { + // Key: { + // GSIPK_eserviceId_descriptorId: { S: eserviceId_descriptorId }, + // }, + // TableName: config.tokenGenerationReadModelTableNameTokenGeneration, + // }; + // const command = new GetItemCommand(input); + // const data: GetItemCommandOutput = await dynamoDBClient.send(command); + + if (!data.Items) { + return undefined; + } else { + const unmarshalled = unmarshall(data.Items[0]); + const tokenStateEntry = + TokenGenerationStatesClientPurposeEntry.safeParse(unmarshalled); + + if (!tokenStateEntry.success) { + throw genericInternalError( + `Unable to parse token state entry item: result ${JSON.stringify( + tokenStateEntry + )} - data ${JSON.stringify(data)} ` + ); + } + return tokenStateEntry.data; + } +}; + +export const sleep = (ms: number, mockDate = new Date()): Promise => + new Promise((resolve) => { + vi.useRealTimers(); + setTimeout(resolve, ms); + vi.useFakeTimers(); + vi.setSystemTime(mockDate); + }); diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 426c4f68fd..d401341881 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -9,16 +9,26 @@ import { vi, } from "vitest"; import { + ClientId, Descriptor, + DescriptorId, EService, EServiceDescriptorActivatedV2, EServiceDescriptorArchivedV2, EServiceDescriptorPublishedV2, EServiceDescriptorSuspendedV2, EServiceEventEnvelope, + EServiceId, ItemState, PlatformStatesCatalogEntry, + PurposeId, + TenantId, + TokenGenerationStatesClientEntry, + TokenGenerationStatesClientPurposeEntry, + clientKind, descriptorState, + generateId, + itemState, toEServiceV2, } from "pagopa-interop-models"; import { @@ -33,7 +43,13 @@ import { getMockEService, getMockDocument, } from "pagopa-interop-commons-test"; -import { readCatalogEntry, writeCatalogEntry } from "../src/utils.js"; +import { + readCatalogEntry, + readTokenStateEntryByEserviceIdAndDescriptorId, + sleep, + writeCatalogEntry, + writeTokenStateEntry, +} from "../src/utils.js"; import { handleMessageV2 } from "../src/consumerServiceV2.js"; import { config } from "./utils.js"; @@ -62,12 +78,35 @@ describe("database test", async () => { const tokenGenerationTableDefinition: CreateTableInput = { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion TableName: config!.tokenGenerationReadModelTableNameTokenGeneration, - AttributeDefinitions: [{ AttributeName: "PK", AttributeType: "S" }], + AttributeDefinitions: [ + { AttributeName: "PK", AttributeType: "S" }, + { AttributeName: "GSIPK_eserviceId_descriptorId", AttributeType: "S" }, + ], KeySchema: [{ AttributeName: "PK", KeyType: "HASH" }], BillingMode: "PAY_PER_REQUEST", + GlobalSecondaryIndexes: [ + { + IndexName: "gsiIndex", + KeySchema: [ + { + AttributeName: "GSIPK_eserviceId_descriptorId", + KeyType: "HASH", + }, + ], + Projection: { + NonKeyAttributes: [], + ProjectionType: "ALL", + }, + // ProvisionedThroughput: { + // ReadCapacityUnits: 5, + // WriteCapacityUnits: 5, + // }, + }, + ], }; const command2 = new CreateTableCommand(tokenGenerationTableDefinition); - await dynamoDBClient.send(command2); + const result = await dynamoDBClient.send(command2); + console.log(result); // const tablesResult = await dynamoDBClient.listTables(); // console.log(tablesResult.TableNames); @@ -86,9 +125,10 @@ describe("database test", async () => { const command2 = new DeleteTableCommand(tableToDelete2); await dynamoDBClient.send(command2); }); + const mockDate = new Date(); beforeAll(() => { vi.useFakeTimers(); - vi.setSystemTime(new Date()); + vi.setSystemTime(mockDate); }); afterAll(() => { vi.useRealTimers(); @@ -96,7 +136,7 @@ describe("database test", async () => { describe("Events V2", async () => { const mockEService = getMockEService(); - it("EServiceDescriptorActivated", async () => { + it.only("EServiceDescriptorActivated", async () => { const suspendedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], @@ -134,25 +174,67 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - const primaryKey = `ESERVICEDESCRIPTOR#${updatedEService.id}#${publishedDescriptor.id}`; + const catalogEntryPrimaryKey = `ESERVICEDESCRIPTOR#${updatedEService.id}#${publishedDescriptor.id}`; const previousStateEntry: PlatformStatesCatalogEntry = { - PK: primaryKey, + PK: catalogEntryPrimaryKey, state: ItemState.Enum.INACTIVE, descriptorAudience: publishedDescriptor.audience[0], version: 1, updatedAt: new Date().toISOString(), }; await writeCatalogEntry(previousStateEntry, dynamoDBClient); + + // token-generation-states + const eserviceId_descriptorId = `${generateId()}#${generateId()}`; + const previousTokenStateEntry: TokenGenerationStatesClientPurposeEntry = { + PK: catalogEntryPrimaryKey, + descriptorState: ItemState.Enum.INACTIVE, + descriptorAudience: publishedDescriptor.audience[0], + updatedAt: new Date().toISOString(), + consumerId: generateId(), + agreementId: generateId(), + purposeVersionId: generateId(), + GSIPK_consumerId_eserviceId: `${generateId()}#${generateId()}`, + clientKind: clientKind.consumer, + publicKey: "PEM", + GSIPK_clientId: generateId(), + GSIPK_kid: "KID", + GSIPK_clientId_purposeId: `${generateId()}#${generateId()}`, + agreementState: "ACTIVE", + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + GSIPK_purposeId: generateId(), + purposeState: itemState.inactive, + }; + await writeTokenStateEntry(previousTokenStateEntry, dynamoDBClient); + await sleep(1000, mockDate); + await handleMessageV2(message, dynamoDBClient); - const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); - const expectedEntry: PlatformStatesCatalogEntry = { + // platform-states + const retrievedCatalogEntry = await readCatalogEntry( + catalogEntryPrimaryKey, + dynamoDBClient + ); + const expectedCatalogEntry: PlatformStatesCatalogEntry = { ...previousStateEntry, state: ItemState.Enum.ACTIVE, version: 2, updatedAt: new Date().toISOString(), }; - expect(retrievedEntry).toEqual(expectedEntry); + expect(retrievedCatalogEntry).toEqual(expectedCatalogEntry); + + // token-generation-states + const retrievedTokenStateEntry = + await readTokenStateEntryByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry: TokenGenerationStatesClientPurposeEntry = { + ...previousTokenStateEntry, + descriptorState: ItemState.Enum.ACTIVE, + updatedAt: new Date().toISOString(), + }; + expect(retrievedTokenStateEntry).toEqual(expectedTokenStateEntry); }); it("EServiceDescriptorArchived", async () => { diff --git a/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts b/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts index e914ccfdd6..e1f8d64de2 100644 --- a/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts +++ b/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts @@ -1,14 +1,20 @@ import { z } from "zod"; import { ClientKind } from "../authorization/client.js"; -import { AgreementId, PurposeVersionId } from "../brandedIds.js"; +import { + AgreementId, + ClientId, + PurposeId, + PurposeVersionId, + TenantId, +} from "../brandedIds.js"; import { ItemState } from "./platform-states-entry.js"; const TokenGenerationStatesBaseEntry = z.object({ PK: z.string(), - consumerId: z.string(), + consumerId: TenantId, clientKind: ClientKind, publicKey: z.string(), - GSIPK_clientId: z.string(), + GSIPK_clientId: ClientId, GSIPK_kid: z.string(), GSIPK_clientId_purposeId: z.string(), updatedAt: z.string().datetime(), @@ -18,15 +24,14 @@ type TokenGenerationStatesBaseEntry = z.infer< >; export const TokenGenerationStatesClientPurposeEntry = - TokenGenerationStatesBaseEntry && - z.object({ + TokenGenerationStatesBaseEntry.extend({ GSIPK_consumerId_eserviceId: z.string(), agreementId: AgreementId, agreementState: ItemState, GSIPK_eserviceId_descriptorId: z.string(), descriptorState: ItemState, descriptorAudience: z.string(), - GSIPK_purposeId: z.string(), + GSIPK_purposeId: PurposeId, purposeState: ItemState, purposeVersionId: PurposeVersionId, }); From e72fea1c31b7a08f16cb27b796c8fc2ee19364e9 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 29 Aug 2024 15:07:11 +0200 Subject: [PATCH 033/241] Fix --- .../test/catalogPlatformstateWriter.integration.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index d401341881..c55d6b3ddd 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -174,7 +174,7 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - const catalogEntryPrimaryKey = `ESERVICEDESCRIPTOR#${updatedEService.id}#${publishedDescriptor.id}`; + const catalogEntryPrimaryKey = `ESERVICEDESCRIPTOR#${eservice.id}#${publishedDescriptor.id}`; const previousStateEntry: PlatformStatesCatalogEntry = { PK: catalogEntryPrimaryKey, state: ItemState.Enum.INACTIVE, @@ -185,7 +185,7 @@ describe("database test", async () => { await writeCatalogEntry(previousStateEntry, dynamoDBClient); // token-generation-states - const eserviceId_descriptorId = `${generateId()}#${generateId()}`; + const eserviceId_descriptorId = `${eservice.id}#${publishedDescriptor.id}`; const previousTokenStateEntry: TokenGenerationStatesClientPurposeEntry = { PK: catalogEntryPrimaryKey, descriptorState: ItemState.Enum.INACTIVE, From 3eb80393757381cf1c077e1039e3013aba9075ec Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 29 Aug 2024 15:24:02 +0200 Subject: [PATCH 034/241] Draft --- .../src/consumerServiceV2.ts | 24 +++++++++ .../catalog-platformstate-writer/src/utils.ts | 51 +++++++++++++++---- ...logPlatformstateWriter.integration.test.ts | 37 ++++++++++++++ 3 files changed, 103 insertions(+), 9 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index 8318cc37fc..afa68c61b3 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -1,5 +1,6 @@ import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; import { + descriptorState, DescriptorState, EServiceEventEnvelopeV2, fromEServiceDescriptorStateV2, @@ -13,6 +14,7 @@ import { deleteCatalogEntry, descriptorStateToClientState, readCatalogEntry, + readTokenStateEntriesByEserviceIdAndDescriptorId, readTokenStateEntryByEserviceIdAndDescriptorId, updateDescriptorState, writeCatalogEntry, @@ -121,6 +123,28 @@ export async function handleMessageV2( const primaryKey = `ESERVICEDESCRIPTOR#${eservice.id}#${msg.data.descriptorId}`; await deleteCatalogEntry(primaryKey, dynamoDBClient); + + // token-generation-states + const descriptorId = msg.data.descriptorId; + const eserviceId_descriptorId = `${eservice.id}#${descriptorId}`; + const result = await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + + if (result) { + for (const entry of result) { + await updateDescriptorState( + dynamoDBClient, + entry.PK, + descriptorStateToClientState(descriptorState.archived) + ); + } + } else { + throw genericInternalError( + `Unable to find token generation state entry with GSIPK_eserviceId_descriptorId ${eserviceId_descriptorId}` + ); + } }) .with( { type: "EServiceDeleted" }, diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index 371c242a5f..e6c3cffb73 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -26,6 +26,7 @@ import { UpdateItemInput, } from "@aws-sdk/client-dynamodb"; import { unmarshall } from "@aws-sdk/util-dynamodb"; +import { z } from "zod"; import { config } from "./config/config.js"; export const writeCatalogEntry = async ( @@ -235,15 +236,6 @@ export const readTokenStateEntryByEserviceIdAndDescriptorId = async ( const data: QueryCommandOutput = await dynamoDBClient.send(command); console.log("data.Items ", data); - // const input: GetItemInput = { - // Key: { - // GSIPK_eserviceId_descriptorId: { S: eserviceId_descriptorId }, - // }, - // TableName: config.tokenGenerationReadModelTableNameTokenGeneration, - // }; - // const command = new GetItemCommand(input); - // const data: GetItemCommandOutput = await dynamoDBClient.send(command); - if (!data.Items) { return undefined; } else { @@ -262,6 +254,47 @@ export const readTokenStateEntryByEserviceIdAndDescriptorId = async ( } }; +export const readTokenStateEntriesByEserviceIdAndDescriptorId = async ( + eserviceId_descriptorId: string, + dynamoDBClient: DynamoDBClient +): Promise => { + console.log("eserviceId_descriptorId ", eserviceId_descriptorId); + const input: QueryInput = { + TableName: config.tokenGenerationReadModelTableNameTokenGeneration, + IndexName: "gsiIndex", // Use the name of your Global Secondary Index + KeyConditionExpression: `GSIPK_eserviceId_descriptorId = :gsi_value`, + ExpressionAttributeValues: { + ":gsi_value": { S: eserviceId_descriptorId }, + }, + // ExpressionAttributeNames: { + // "#gsi": "GSIPK_eserviceId_descriptorId", + // }, + ScanIndexForward: false, + }; + const command = new QueryCommand(input); + const data: QueryCommandOutput = await dynamoDBClient.send(command); + console.log("data.Items ", data); + + if (!data.Items) { + return undefined; + } else { + const unmarshalledItems = data.Items.map((item) => unmarshall(item)); + + const tokenStateEntries = z + .array(TokenGenerationStatesClientPurposeEntry) + .safeParse(unmarshalledItems); + + if (!tokenStateEntries.success) { + throw genericInternalError( + `Unable to parse token state entry item: result ${JSON.stringify( + tokenStateEntries + )} - data ${JSON.stringify(data)} ` + ); + } + return tokenStateEntries.data; + } +}; + export const sleep = (ms: number, mockDate = new Date()): Promise => new Promise((resolve) => { vi.useRealTimers(); diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index c55d6b3ddd..2dc30b9b11 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -282,10 +282,47 @@ describe("database test", async () => { updatedAt: new Date().toISOString(), }; await writeCatalogEntry(previousStateEntry, dynamoDBClient); + + const eserviceId_descriptorId = `${eservice.id}#${publishedDescriptor.id}`; + const previousTokenStateEntry: TokenGenerationStatesClientPurposeEntry = { + PK: primaryKey, + descriptorState: ItemState.Enum.INACTIVE, + descriptorAudience: publishedDescriptor.audience[0], + updatedAt: new Date().toISOString(), + consumerId: generateId(), + agreementId: generateId(), + purposeVersionId: generateId(), + GSIPK_consumerId_eserviceId: `${generateId()}#${generateId()}`, + clientKind: clientKind.consumer, + publicKey: "PEM", + GSIPK_clientId: generateId(), + GSIPK_kid: "KID", + GSIPK_clientId_purposeId: `${generateId()}#${generateId()}`, + agreementState: "ACTIVE", + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + GSIPK_purposeId: generateId(), + purposeState: itemState.inactive, + }; + await writeTokenStateEntry(previousTokenStateEntry, dynamoDBClient); + await sleep(1000, mockDate); + await handleMessageV2(message, dynamoDBClient); const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); expect(retrievedEntry).toBeUndefined(); + + // token-generation-states + const retrievedTokenStateEntry = + await readTokenStateEntryByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry: TokenGenerationStatesClientPurposeEntry = { + ...previousTokenStateEntry, + descriptorState: ItemState.Enum.ACTIVE, + updatedAt: new Date().toISOString(), + }; + expect(retrievedTokenStateEntry).toEqual(expectedTokenStateEntry); }); it("EServiceDescriptorPublished", async () => { From 640db7c3e825c93efb3ce94d461157072c35b0f8 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 29 Aug 2024 16:10:05 +0200 Subject: [PATCH 035/241] Draft --- ...logPlatformstateWriter.integration.test.ts | 93 ++++++++++++------- 1 file changed, 62 insertions(+), 31 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 2dc30b9b11..82bf526283 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -11,7 +11,6 @@ import { import { ClientId, Descriptor, - DescriptorId, EService, EServiceDescriptorActivatedV2, EServiceDescriptorArchivedV2, @@ -23,7 +22,6 @@ import { PlatformStatesCatalogEntry, PurposeId, TenantId, - TokenGenerationStatesClientEntry, TokenGenerationStatesClientPurposeEntry, clientKind, descriptorState, @@ -45,6 +43,7 @@ import { } from "pagopa-interop-commons-test"; import { readCatalogEntry, + readTokenStateEntriesByEserviceIdAndDescriptorId, readTokenStateEntryByEserviceIdAndDescriptorId, sleep, writeCatalogEntry, @@ -237,7 +236,7 @@ describe("database test", async () => { expect(retrievedTokenStateEntry).toEqual(expectedTokenStateEntry); }); - it("EServiceDescriptorArchived", async () => { + it.only("EServiceDescriptorArchived", async () => { const publishedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], @@ -284,26 +283,49 @@ describe("database test", async () => { await writeCatalogEntry(previousStateEntry, dynamoDBClient); const eserviceId_descriptorId = `${eservice.id}#${publishedDescriptor.id}`; - const previousTokenStateEntry: TokenGenerationStatesClientPurposeEntry = { - PK: primaryKey, - descriptorState: ItemState.Enum.INACTIVE, - descriptorAudience: publishedDescriptor.audience[0], - updatedAt: new Date().toISOString(), - consumerId: generateId(), - agreementId: generateId(), - purposeVersionId: generateId(), - GSIPK_consumerId_eserviceId: `${generateId()}#${generateId()}`, - clientKind: clientKind.consumer, - publicKey: "PEM", - GSIPK_clientId: generateId(), - GSIPK_kid: "KID", - GSIPK_clientId_purposeId: `${generateId()}#${generateId()}`, - agreementState: "ACTIVE", - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - GSIPK_purposeId: generateId(), - purposeState: itemState.inactive, - }; - await writeTokenStateEntry(previousTokenStateEntry, dynamoDBClient); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + PK: "TO DO client kid purpose 1", + descriptorState: ItemState.Enum.INACTIVE, + descriptorAudience: publishedDescriptor.audience[0], + updatedAt: new Date().toISOString(), + consumerId: generateId(), + agreementId: generateId(), + purposeVersionId: generateId(), + GSIPK_consumerId_eserviceId: `${generateId()}#${generateId()}`, + clientKind: clientKind.consumer, + publicKey: "PEM", + GSIPK_clientId: generateId(), + GSIPK_kid: "KID", + GSIPK_clientId_purposeId: `${generateId()}#${generateId()}`, + agreementState: "ACTIVE", + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + GSIPK_purposeId: generateId(), + purposeState: itemState.inactive, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + PK: "TO DO client kid purpose 2", + descriptorState: ItemState.Enum.INACTIVE, + descriptorAudience: publishedDescriptor.audience[0], + updatedAt: new Date().toISOString(), + consumerId: generateId(), + agreementId: generateId(), + purposeVersionId: generateId(), + GSIPK_consumerId_eserviceId: `${generateId()}#${generateId()}`, + clientKind: clientKind.consumer, + publicKey: "PEM", + GSIPK_clientId: generateId(), + GSIPK_kid: "KID", + GSIPK_clientId_purposeId: `${generateId()}#${generateId()}`, + agreementState: "ACTIVE", + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + GSIPK_purposeId: generateId(), + purposeState: itemState.inactive, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); await sleep(1000, mockDate); await handleMessageV2(message, dynamoDBClient); @@ -312,17 +334,26 @@ describe("database test", async () => { expect(retrievedEntry).toBeUndefined(); // token-generation-states - const retrievedTokenStateEntry = - await readTokenStateEntryByEserviceIdAndDescriptorId( + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( eserviceId_descriptorId, dynamoDBClient ); - const expectedTokenStateEntry: TokenGenerationStatesClientPurposeEntry = { - ...previousTokenStateEntry, - descriptorState: ItemState.Enum.ACTIVE, - updatedAt: new Date().toISOString(), - }; - expect(retrievedTokenStateEntry).toEqual(expectedTokenStateEntry); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: ItemState.Enum.INACTIVE, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: ItemState.Enum.INACTIVE, + }; + expect(retrievedTokenStateEntries).toEqual([ + expectedTokenStateEntry2, + expectedTokenStateEntry1, // To do: understand how these are sorted + ]); }); it("EServiceDescriptorPublished", async () => { From d22a9af27f65465cb2698600737516d95117418e Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Thu, 29 Aug 2024 17:00:58 +0200 Subject: [PATCH 036/241] Fix wrong token state pk --- .../test/catalogPlatformstateWriter.integration.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 82bf526283..0bd6791036 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -184,9 +184,11 @@ describe("database test", async () => { await writeCatalogEntry(previousStateEntry, dynamoDBClient); // token-generation-states + // TODO: add kid type + const tokenStateEntryPK = `CLIENTKID#${generateId()}#${generateId()}`; const eserviceId_descriptorId = `${eservice.id}#${publishedDescriptor.id}`; const previousTokenStateEntry: TokenGenerationStatesClientPurposeEntry = { - PK: catalogEntryPrimaryKey, + PK: tokenStateEntryPK, descriptorState: ItemState.Enum.INACTIVE, descriptorAudience: publishedDescriptor.audience[0], updatedAt: new Date().toISOString(), From e3e765a4aa8341a610093eeb387440079537d5be Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Thu, 29 Aug 2024 18:51:17 +0200 Subject: [PATCH 037/241] Change token state entry to entries in EServiceDescriptorActivated --- .../src/consumerServiceV2.ts | 18 +-- .../catalog-platformstate-writer/src/utils.ts | 2 +- ...logPlatformstateWriter.integration.test.ts | 105 ++++++++++++------ 3 files changed, 83 insertions(+), 42 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index afa68c61b3..41c75e91fb 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -15,7 +15,7 @@ import { descriptorStateToClientState, readCatalogEntry, readTokenStateEntriesByEserviceIdAndDescriptorId, - readTokenStateEntryByEserviceIdAndDescriptorId, + readTokenStateEntryByEServiceIdAndDescriptorId, updateDescriptorState, writeCatalogEntry, } from "./utils.js"; @@ -94,20 +94,22 @@ export async function handleMessageV2( // token-generation-states const eserviceId_descriptorId = `${eservice.id}#${descriptorId}`; - const result = await readTokenStateEntryByEserviceIdAndDescriptorId( + const result = await readTokenStateEntriesByEserviceIdAndDescriptorId( eserviceId_descriptorId, dynamoDBClient ); if (result) { - await updateDescriptorState( - dynamoDBClient, - result.PK, - updatedCatalogEntry.state - ); + for (const entry of result) { + await updateDescriptorState( + dynamoDBClient, + entry.PK, + updatedCatalogEntry.state + ); + } } else { throw genericInternalError( - `Unable to find token generation state entry with GSIPK_eserviceId_descriptorId ${eserviceId_descriptorId}` + `Unable to find token generation state entries with GSIPK_eserviceId_descriptorId ${eserviceId_descriptorId}` ); } } diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index e6c3cffb73..35258af353 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -215,7 +215,7 @@ export const writeTokenStateEntry = async ( console.log("write token state", await dynamoDBClient.send(command)); }; -export const readTokenStateEntryByEserviceIdAndDescriptorId = async ( +export const readTokenStateEntryByEServiceIdAndDescriptorId = async ( eserviceId_descriptorId: string, dynamoDBClient: DynamoDBClient ): Promise => { diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 0bd6791036..1a7ac5ba86 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -44,7 +44,6 @@ import { import { readCatalogEntry, readTokenStateEntriesByEserviceIdAndDescriptorId, - readTokenStateEntryByEserviceIdAndDescriptorId, sleep, writeCatalogEntry, writeTokenStateEntry, @@ -85,6 +84,7 @@ describe("database test", async () => { BillingMode: "PAY_PER_REQUEST", GlobalSecondaryIndexes: [ { + // TODO: change index name IndexName: "gsiIndex", KeySchema: [ { @@ -184,31 +184,55 @@ describe("database test", async () => { await writeCatalogEntry(previousStateEntry, dynamoDBClient); // token-generation-states - // TODO: add kid type - const tokenStateEntryPK = `CLIENTKID#${generateId()}#${generateId()}`; + // TODO: replace last generateId() with kid + const tokenStateEntryPK1 = `CLIENTKID#${generateId()}#${generateId()}`; const eserviceId_descriptorId = `${eservice.id}#${publishedDescriptor.id}`; - const previousTokenStateEntry: TokenGenerationStatesClientPurposeEntry = { - PK: tokenStateEntryPK, - descriptorState: ItemState.Enum.INACTIVE, - descriptorAudience: publishedDescriptor.audience[0], - updatedAt: new Date().toISOString(), - consumerId: generateId(), - agreementId: generateId(), - purposeVersionId: generateId(), - GSIPK_consumerId_eserviceId: `${generateId()}#${generateId()}`, - clientKind: clientKind.consumer, - publicKey: "PEM", - GSIPK_clientId: generateId(), - GSIPK_kid: "KID", - GSIPK_clientId_purposeId: `${generateId()}#${generateId()}`, - agreementState: "ACTIVE", - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - GSIPK_purposeId: generateId(), - purposeState: itemState.inactive, - }; - await writeTokenStateEntry(previousTokenStateEntry, dynamoDBClient); - await sleep(1000, mockDate); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + PK: tokenStateEntryPK1, + descriptorState: ItemState.Enum.INACTIVE, + descriptorAudience: publishedDescriptor.audience[0], + updatedAt: new Date().toISOString(), + consumerId: generateId(), + agreementId: generateId(), + purposeVersionId: generateId(), + GSIPK_consumerId_eserviceId: `${generateId()}#${generateId()}`, + clientKind: clientKind.consumer, + publicKey: "PEM", + GSIPK_clientId: generateId(), + GSIPK_kid: "KID", + GSIPK_clientId_purposeId: `${generateId()}#${generateId()}`, + agreementState: "ACTIVE", + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + GSIPK_purposeId: generateId(), + purposeState: itemState.inactive, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + const tokenStateEntryPK2 = `CLIENTKID#${generateId()}#${generateId()}`; + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + PK: tokenStateEntryPK2, + descriptorState: ItemState.Enum.ACTIVE, + descriptorAudience: publishedDescriptor.audience[0], + updatedAt: new Date().toISOString(), + consumerId: generateId(), + agreementId: generateId(), + purposeVersionId: generateId(), + GSIPK_consumerId_eserviceId: `${generateId()}#${generateId()}`, + clientKind: clientKind.consumer, + publicKey: "PEM", + GSIPK_clientId: generateId(), + GSIPK_kid: "KID", + GSIPK_clientId_purposeId: `${generateId()}#${generateId()}`, + agreementState: "ACTIVE", + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + GSIPK_purposeId: generateId(), + purposeState: itemState.inactive, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + + await sleep(1000, mockDate); await handleMessageV2(message, dynamoDBClient); // platform-states @@ -225,20 +249,35 @@ describe("database test", async () => { expect(retrievedCatalogEntry).toEqual(expectedCatalogEntry); // token-generation-states - const retrievedTokenStateEntry = - await readTokenStateEntryByEserviceIdAndDescriptorId( + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( eserviceId_descriptorId, dynamoDBClient ); - const expectedTokenStateEntry: TokenGenerationStatesClientPurposeEntry = { - ...previousTokenStateEntry, - descriptorState: ItemState.Enum.ACTIVE, - updatedAt: new Date().toISOString(), - }; - expect(retrievedTokenStateEntry).toEqual(expectedTokenStateEntry); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: ItemState.Enum.ACTIVE, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: ItemState.Enum.ACTIVE, + updatedAt: new Date().toISOString(), + }; + + // TODO: this works, but arrayContaining must have the exact objects + // expect.arrayContaining([expectedTokenStateEntry2, expectedTokenStateEntry2]) also passes the test + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry2, + expectedTokenStateEntry1, + ]) + ); }); - it.only("EServiceDescriptorArchived", async () => { + it("EServiceDescriptorArchived", async () => { const publishedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], From 7c3bf312cfeb1e741c740225501d530a2c103b14 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Thu, 29 Aug 2024 19:04:57 +0200 Subject: [PATCH 038/241] Replace ItemState.Enum with itemState --- .../src/consumerServiceV2.ts | 6 ++--- .../catalog-platformstate-writer/src/utils.ts | 5 ++-- ...logPlatformstateWriter.integration.test.ts | 26 +++++++++---------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index ab8b685645..c70f958a15 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -4,7 +4,7 @@ import { EServiceEventEnvelopeV2, fromEServiceDescriptorStateV2, genericInternalError, - ItemState, + itemState, PlatformStatesCatalogEntry, } from "pagopa-interop-models"; import { match } from "ts-pattern"; @@ -78,8 +78,8 @@ export async function handleMessageV2( ...catalogEntry, state: msg.type === "EServiceDescriptorActivated" - ? ItemState.Enum.ACTIVE - : ItemState.Enum.INACTIVE, + ? itemState.active + : itemState.inactive, version: msg.version, updatedAt: new Date().toISOString(), }; diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index 634ca284c5..6bcdfeef98 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -2,6 +2,7 @@ import { descriptorState, DescriptorState, genericInternalError, + itemState, ItemState, PlatformStatesCatalogEntry, } from "pagopa-interop-models"; @@ -109,8 +110,8 @@ export const descriptorStateToClientState = ( state: DescriptorState ): ItemState => state === descriptorState.published || state === descriptorState.deprecated - ? ItemState.Enum.ACTIVE - : ItemState.Enum.INACTIVE; + ? itemState.active + : itemState.inactive; export const sleep = (ms: number, mockDate = new Date()): Promise => new Promise((resolve) => { diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 0844e0cc31..27d319d2d9 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -17,9 +17,9 @@ import { EServiceDescriptorSuspendedV2, EServiceDescriptorUpdatedV1, EServiceEventEnvelope, - ItemState, PlatformStatesCatalogEntry, descriptorState, + itemState, toEServiceV2, } from "pagopa-interop-models"; import { @@ -135,7 +135,7 @@ describe("database test", async () => { const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); const expectedEntry: PlatformStatesCatalogEntry = { PK: primaryKey, - state: ItemState.Enum.ACTIVE, + state: itemState.active, descriptorAudience: publishedDescriptor.audience[0], version: 2, updatedAt: new Date().toISOString(), @@ -181,7 +181,7 @@ describe("database test", async () => { const primaryKey = `ESERVICEDESCRIPTOR#${eservice.id}#${publishedDescriptor.id}`; const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, - state: ItemState.Enum.INACTIVE, + state: itemState.inactive, descriptorAudience: publishedDescriptor.audience[0], version: 1, updatedAt: new Date().toISOString(), @@ -193,7 +193,7 @@ describe("database test", async () => { const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); const expectedEntry: PlatformStatesCatalogEntry = { ...previousStateEntry, - state: ItemState.Enum.ACTIVE, + state: itemState.active, version: 2, }; expect(retrievedEntry).toEqual(expectedEntry); @@ -235,7 +235,7 @@ describe("database test", async () => { const primaryKey = `ESERVICEDESCRIPTOR#${eservice.id}#${publishedDescriptor.id}`; const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, - state: ItemState.Enum.ACTIVE, + state: itemState.active, descriptorAudience: publishedDescriptor.audience[0], version: 1, updatedAt: new Date().toISOString(), @@ -247,7 +247,7 @@ describe("database test", async () => { const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); const expectedEntry: PlatformStatesCatalogEntry = { ...previousStateEntry, - state: ItemState.Enum.INACTIVE, + state: itemState.inactive, version: 2, }; expect(retrievedEntry).toEqual(expectedEntry); @@ -290,7 +290,7 @@ describe("database test", async () => { const primaryKey = `ESERVICEDESCRIPTOR#${eservice.id}#${publishedDescriptor.id}`; const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, - state: ItemState.Enum.INACTIVE, + state: itemState.inactive, descriptorAudience: publishedDescriptor.audience[0], version: 1, updatedAt: new Date().toISOString(), @@ -347,7 +347,7 @@ describe("database test", async () => { const primaryKey = `ESERVICEDESCRIPTOR#${updatedEService.id}#${publishedDescriptor.id}`; const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, - state: ItemState.Enum.INACTIVE, + state: itemState.inactive, descriptorAudience: publishedDescriptor.audience[0], version: 1, updatedAt: new Date().toISOString(), @@ -358,7 +358,7 @@ describe("database test", async () => { const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); const expectedEntry: PlatformStatesCatalogEntry = { ...previousStateEntry, - state: ItemState.Enum.ACTIVE, + state: itemState.active, version: 2, updatedAt: new Date().toISOString(), }; @@ -404,7 +404,7 @@ describe("database test", async () => { const primaryKey = `ESERVICEDESCRIPTOR#${updatedEService.id}#${publishedDescriptor.id}`; const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, - state: ItemState.Enum.INACTIVE, + state: itemState.inactive, descriptorAudience: publishedDescriptor.audience[0], version: 1, updatedAt: new Date().toISOString(), @@ -457,7 +457,7 @@ describe("database test", async () => { const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); const expectedEntry: PlatformStatesCatalogEntry = { PK: primaryKey, - state: ItemState.Enum.ACTIVE, + state: itemState.active, descriptorAudience: publishedDescriptor.audience[0], version: 2, updatedAt: new Date().toISOString(), @@ -504,7 +504,7 @@ describe("database test", async () => { const primaryKey = `ESERVICEDESCRIPTOR#${updatedEService.id}#${publishedDescriptor.id}`; const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, - state: ItemState.Enum.ACTIVE, + state: itemState.active, descriptorAudience: publishedDescriptor.audience[0], version: 1, updatedAt: new Date().toISOString(), @@ -515,7 +515,7 @@ describe("database test", async () => { const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); const expectedEntry: PlatformStatesCatalogEntry = { ...previousStateEntry, - state: ItemState.Enum.INACTIVE, + state: itemState.inactive, version: 2, updatedAt: new Date().toISOString(), }; From 2a6fdd58c30994fdcb60a87d1f13824cb03ef8dc Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Fri, 30 Aug 2024 09:32:06 +0200 Subject: [PATCH 039/241] Fix merge IMN-798 --- .../catalog-platformstate-writer/src/consumerServiceV2.ts | 1 - packages/catalog-platformstate-writer/src/utils.ts | 6 +++--- .../test/catalogPlatformstateWriter.integration.test.ts | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index 555111b936..beca6d711e 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -15,7 +15,6 @@ import { descriptorStateToClientState, readCatalogEntry, readTokenStateEntriesByEserviceIdAndDescriptorId, - readTokenStateEntryByEServiceIdAndDescriptorId, updateDescriptorState, writeCatalogEntry, } from "./utils.js"; diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index db9b294126..dc8bad560f 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -3,8 +3,8 @@ import { descriptorState, DescriptorState, genericInternalError, - itemState, ItemState, + itemState, PlatformStatesCatalogEntry, TokenGenerationStatesClientPurposeEntry, } from "pagopa-interop-models"; @@ -117,8 +117,8 @@ export const descriptorStateToClientState = ( state: DescriptorState ): ItemState => state === descriptorState.published || state === descriptorState.deprecated - ? ItemState.Enum.ACTIVE - : ItemState.Enum.INACTIVE; + ? itemState.active + : itemState.inactive; export const updateDescriptorState = async ( dynamoDBClient: DynamoDBClient, diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 252bffe17b..a038fb9e99 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -347,7 +347,7 @@ describe("database test", async () => { describe("Events V2", async () => { const mockEService = getMockEService(); - it.only("EServiceDescriptorActivated", async () => { + it("EServiceDescriptorActivated", async () => { const suspendedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], From 9ccbf31207a2a1ae1c6b4183b4c34fa6c76bcb12 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Fri, 30 Aug 2024 09:46:30 +0200 Subject: [PATCH 040/241] Replace ItemState.Enum with itemState --- .../src/consumerServiceV2.ts | 7 ++--- .../catalog-platformstate-writer/src/utils.ts | 5 ++-- ...logPlatformstateWriter.integration.test.ts | 28 +++++++++---------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index 41c75e91fb..beca6d711e 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -6,7 +6,7 @@ import { fromEServiceDescriptorStateV2, fromEServiceV2, genericInternalError, - ItemState, + itemState, PlatformStatesCatalogEntry, } from "pagopa-interop-models"; import { match } from "ts-pattern"; @@ -15,7 +15,6 @@ import { descriptorStateToClientState, readCatalogEntry, readTokenStateEntriesByEserviceIdAndDescriptorId, - readTokenStateEntryByEServiceIdAndDescriptorId, updateDescriptorState, writeCatalogEntry, } from "./utils.js"; @@ -85,8 +84,8 @@ export async function handleMessageV2( ...catalogEntry, state: msg.type === "EServiceDescriptorActivated" - ? ItemState.Enum.ACTIVE - : ItemState.Enum.INACTIVE, + ? itemState.active + : itemState.inactive, version: msg.version, updatedAt: new Date().toISOString(), }; diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index 35258af353..5dca2b6177 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -3,6 +3,7 @@ import { descriptorState, DescriptorState, genericInternalError, + itemState, ItemState, PlatformStatesCatalogEntry, TokenGenerationStatesClientPurposeEntry, @@ -116,8 +117,8 @@ export const descriptorStateToClientState = ( state: DescriptorState ): ItemState => state === descriptorState.published || state === descriptorState.deprecated - ? ItemState.Enum.ACTIVE - : ItemState.Enum.INACTIVE; + ? itemState.active + : itemState.inactive; export const updateDescriptorState = async ( dynamoDBClient: DynamoDBClient, diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 1a7ac5ba86..5ba548106a 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -176,7 +176,7 @@ describe("database test", async () => { const catalogEntryPrimaryKey = `ESERVICEDESCRIPTOR#${eservice.id}#${publishedDescriptor.id}`; const previousStateEntry: PlatformStatesCatalogEntry = { PK: catalogEntryPrimaryKey, - state: ItemState.Enum.INACTIVE, + state: itemState.inactive, descriptorAudience: publishedDescriptor.audience[0], version: 1, updatedAt: new Date().toISOString(), @@ -190,7 +190,7 @@ describe("database test", async () => { const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { PK: tokenStateEntryPK1, - descriptorState: ItemState.Enum.INACTIVE, + descriptorState: itemState.inactive, descriptorAudience: publishedDescriptor.audience[0], updatedAt: new Date().toISOString(), consumerId: generateId(), @@ -213,7 +213,7 @@ describe("database test", async () => { const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { PK: tokenStateEntryPK2, - descriptorState: ItemState.Enum.ACTIVE, + descriptorState: itemState.active, descriptorAudience: publishedDescriptor.audience[0], updatedAt: new Date().toISOString(), consumerId: generateId(), @@ -242,7 +242,7 @@ describe("database test", async () => { ); const expectedCatalogEntry: PlatformStatesCatalogEntry = { ...previousStateEntry, - state: ItemState.Enum.ACTIVE, + state: itemState.active, version: 2, updatedAt: new Date().toISOString(), }; @@ -257,13 +257,13 @@ describe("database test", async () => { const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { ...previousTokenStateEntry1, - descriptorState: ItemState.Enum.ACTIVE, + descriptorState: itemState.active, updatedAt: new Date().toISOString(), }; const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { ...previousTokenStateEntry2, - descriptorState: ItemState.Enum.ACTIVE, + descriptorState: itemState.active, updatedAt: new Date().toISOString(), }; @@ -316,7 +316,7 @@ describe("database test", async () => { const primaryKey = `ESERVICEDESCRIPTOR#${updatedEService.id}#${publishedDescriptor.id}`; const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, - state: ItemState.Enum.INACTIVE, + state: itemState.inactive, descriptorAudience: publishedDescriptor.audience[0], version: 1, updatedAt: new Date().toISOString(), @@ -327,7 +327,7 @@ describe("database test", async () => { const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { PK: "TO DO client kid purpose 1", - descriptorState: ItemState.Enum.INACTIVE, + descriptorState: itemState.inactive, descriptorAudience: publishedDescriptor.audience[0], updatedAt: new Date().toISOString(), consumerId: generateId(), @@ -349,7 +349,7 @@ describe("database test", async () => { const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { PK: "TO DO client kid purpose 2", - descriptorState: ItemState.Enum.INACTIVE, + descriptorState: itemState.inactive, descriptorAudience: publishedDescriptor.audience[0], updatedAt: new Date().toISOString(), consumerId: generateId(), @@ -383,13 +383,13 @@ describe("database test", async () => { const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { ...previousTokenStateEntry1, - descriptorState: ItemState.Enum.INACTIVE, + descriptorState: itemState.inactive, updatedAt: new Date().toISOString(), }; const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { ...previousTokenStateEntry2, - descriptorState: ItemState.Enum.INACTIVE, + descriptorState: itemState.inactive, }; expect(retrievedTokenStateEntries).toEqual([ expectedTokenStateEntry2, @@ -438,7 +438,7 @@ describe("database test", async () => { const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); const expectedEntry: PlatformStatesCatalogEntry = { PK: primaryKey, - state: ItemState.Enum.ACTIVE, + state: itemState.active, descriptorAudience: publishedDescriptor.audience[0], version: 2, updatedAt: new Date().toISOString(), @@ -485,7 +485,7 @@ describe("database test", async () => { const primaryKey = `ESERVICEDESCRIPTOR#${updatedEService.id}#${publishedDescriptor.id}`; const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, - state: ItemState.Enum.ACTIVE, + state: itemState.active, descriptorAudience: publishedDescriptor.audience[0], version: 1, updatedAt: new Date().toISOString(), @@ -496,7 +496,7 @@ describe("database test", async () => { const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); const expectedEntry: PlatformStatesCatalogEntry = { ...previousStateEntry, - state: ItemState.Enum.INACTIVE, + state: itemState.inactive, version: 2, updatedAt: new Date().toISOString(), }; From 1329e2787ed48835d7871981bd568ccea5388297 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Fri, 30 Aug 2024 11:25:04 +0200 Subject: [PATCH 041/241] Improve platform-states PK types --- .../platform-states-entry.ts | 63 +++++++++++++++++-- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/packages/models/src/token-generation-readmodel/platform-states-entry.ts b/packages/models/src/token-generation-readmodel/platform-states-entry.ts index ecd7eedeaa..81fa5cd4ac 100644 --- a/packages/models/src/token-generation-readmodel/platform-states-entry.ts +++ b/packages/models/src/token-generation-readmodel/platform-states-entry.ts @@ -1,5 +1,8 @@ import { z } from "zod"; import { + AgreementId, + ClientId, + DescriptorId, EServiceId, PurposeId, PurposeVersionId, @@ -17,15 +20,59 @@ export const ItemState = z.enum([ export type ItemState = z.infer; /* -type PrimaryKeyPlatformStates = - | `ESERVICEDESCRIPTOR#${EServiceId}#${DescriptorId}` - | `AGREEMENT#${AgreementId}` - | `PURPOSE#${PurposeId}` - | `CLIENT#${ClientId}`; +export const PlatformStatesEServiceDescriptorPK = z.literal( + `ESERVICEDESCRIPTOR#${EServiceId}#${DescriptorId}` +); +export type PlatformStatesEServiceDescriptorPK = z.infer< + typeof PlatformStatesEServiceDescriptorPK +>; + +const a: PlatformStatesEServiceDescriptorPK = `ESERVICEDESCRIPTOR#${generateId()}#${generateId()}`; // OK +const b: PlatformStatesEServiceDescriptorPK = `ESERVICEDESCRIPTOR#${generateId()}#${generateId()}`; // OK +const c: PlatformStatesEServiceDescriptorPK = `ESERVICEDESCRIPTOR#test#test`; // OK +const d: PlatformStatesEServiceDescriptorPK = `ESERVICEDESCRIPTOR#test#`; // OK +const e: PlatformStatesEServiceDescriptorPK = `ESERVICEDESCRIPTOR#test`; // WRONG +const f: PlatformStatesEServiceDescriptorPK = `test#test#test`; // WRONG + +We don't check the structure of the ids because they are treated as strings */ +export const PlatformStatesEServiceDescriptorPK = z + .string() + .brand(`ESERVICEDESCRIPTOR#eServiceId#descriptorId`); +export type PlatformStatesEServiceDescriptorPK = z.infer< + typeof PlatformStatesEServiceDescriptorPK +>; + +export const makePlatformStatesEServiceDescriptorPK = ( + eserviceId: EServiceId, + descriptorId: DescriptorId +): PlatformStatesEServiceDescriptorPK => + `ESERVICEDESCRIPTOR#${eserviceId}#${descriptorId}` as PlatformStatesEServiceDescriptorPK; +export const PlatformStatesAgreementPK = z + .string() + .brand(`AGREEMENT#agreementId`); +export type PlatformStatesAgreementPK = z.infer< + typeof PlatformStatesAgreementPK +>; +export const makePlatformStatesAgreementPK = ( + agreementId: AgreementId +): PlatformStatesAgreementPK => + `AGREEMENT#${agreementId}` as PlatformStatesAgreementPK; + +export const PlatformStatesPurposePK = z.string().brand(`PURPOSE#purposeId`); +export type PlatformStatesPurposePK = z.infer; +export const makePlatformStatesPurposePK = ( + purposeId: PurposeId +): PlatformStatesPurposePK => `PURPOSE#${purposeId}` as PlatformStatesPurposePK; + +export const PlatformStatesClientPK = z.string().brand(`CLIENT#clientId`); +export type PlatformStatesClientPK = z.infer; +export const makePlatformStatesClientPK = ( + clientId: ClientId +): PlatformStatesClientPK => `CLIENT#${clientId}` as PlatformStatesClientPK; + const PlatformStatesBaseEntry = z.object({ - PK: z.string(), state: ItemState, version: z.number(), updatedAt: z.string().datetime(), @@ -33,6 +80,7 @@ const PlatformStatesBaseEntry = z.object({ type PlatformStatesBaseEntry = z.infer; export const PlatformStatesCatalogEntry = PlatformStatesBaseEntry.extend({ + PK: PlatformStatesEServiceDescriptorPK, descriptorAudience: z.string(), }); export type PlatformStatesCatalogEntry = z.infer< @@ -40,6 +88,7 @@ export type PlatformStatesCatalogEntry = z.infer< >; export const PlatformStatesPurposeEntry = PlatformStatesBaseEntry.extend({ + PK: PlatformStatesPurposePK, purposeVersionId: PurposeVersionId, purposeEserviceId: EServiceId, purposeConsumerId: TenantId, @@ -49,6 +98,7 @@ export type PlatformStatesPurposeEntry = z.infer< >; export const PlatformStatesAgreementEntry = PlatformStatesBaseEntry.extend({ + PK: PlatformStatesAgreementPK, GSIPK_consumerId_eserviceId: z.string(), GSISK_agreementTimestamp: z.string().datetime(), agreementDescriptorId: z.string(), @@ -58,6 +108,7 @@ export type PlatformStatesAgreementEntry = z.infer< >; export const PlatformStatesClientEntry = PlatformStatesBaseEntry.extend({ + PK: PlatformStatesClientPK, clientPurposesIds: z.array(PurposeId), }); export type PlatformStatesClientEntry = z.infer< From 8c65af6c5442457d1819d84c9547a9bd39a94778 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Fri, 30 Aug 2024 11:25:04 +0200 Subject: [PATCH 042/241] Improve platform-states PK types --- .../platform-states-entry.ts | 63 +++++++++++++++++-- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/packages/models/src/token-generation-readmodel/platform-states-entry.ts b/packages/models/src/token-generation-readmodel/platform-states-entry.ts index ecd7eedeaa..81fa5cd4ac 100644 --- a/packages/models/src/token-generation-readmodel/platform-states-entry.ts +++ b/packages/models/src/token-generation-readmodel/platform-states-entry.ts @@ -1,5 +1,8 @@ import { z } from "zod"; import { + AgreementId, + ClientId, + DescriptorId, EServiceId, PurposeId, PurposeVersionId, @@ -17,15 +20,59 @@ export const ItemState = z.enum([ export type ItemState = z.infer; /* -type PrimaryKeyPlatformStates = - | `ESERVICEDESCRIPTOR#${EServiceId}#${DescriptorId}` - | `AGREEMENT#${AgreementId}` - | `PURPOSE#${PurposeId}` - | `CLIENT#${ClientId}`; +export const PlatformStatesEServiceDescriptorPK = z.literal( + `ESERVICEDESCRIPTOR#${EServiceId}#${DescriptorId}` +); +export type PlatformStatesEServiceDescriptorPK = z.infer< + typeof PlatformStatesEServiceDescriptorPK +>; + +const a: PlatformStatesEServiceDescriptorPK = `ESERVICEDESCRIPTOR#${generateId()}#${generateId()}`; // OK +const b: PlatformStatesEServiceDescriptorPK = `ESERVICEDESCRIPTOR#${generateId()}#${generateId()}`; // OK +const c: PlatformStatesEServiceDescriptorPK = `ESERVICEDESCRIPTOR#test#test`; // OK +const d: PlatformStatesEServiceDescriptorPK = `ESERVICEDESCRIPTOR#test#`; // OK +const e: PlatformStatesEServiceDescriptorPK = `ESERVICEDESCRIPTOR#test`; // WRONG +const f: PlatformStatesEServiceDescriptorPK = `test#test#test`; // WRONG + +We don't check the structure of the ids because they are treated as strings */ +export const PlatformStatesEServiceDescriptorPK = z + .string() + .brand(`ESERVICEDESCRIPTOR#eServiceId#descriptorId`); +export type PlatformStatesEServiceDescriptorPK = z.infer< + typeof PlatformStatesEServiceDescriptorPK +>; + +export const makePlatformStatesEServiceDescriptorPK = ( + eserviceId: EServiceId, + descriptorId: DescriptorId +): PlatformStatesEServiceDescriptorPK => + `ESERVICEDESCRIPTOR#${eserviceId}#${descriptorId}` as PlatformStatesEServiceDescriptorPK; +export const PlatformStatesAgreementPK = z + .string() + .brand(`AGREEMENT#agreementId`); +export type PlatformStatesAgreementPK = z.infer< + typeof PlatformStatesAgreementPK +>; +export const makePlatformStatesAgreementPK = ( + agreementId: AgreementId +): PlatformStatesAgreementPK => + `AGREEMENT#${agreementId}` as PlatformStatesAgreementPK; + +export const PlatformStatesPurposePK = z.string().brand(`PURPOSE#purposeId`); +export type PlatformStatesPurposePK = z.infer; +export const makePlatformStatesPurposePK = ( + purposeId: PurposeId +): PlatformStatesPurposePK => `PURPOSE#${purposeId}` as PlatformStatesPurposePK; + +export const PlatformStatesClientPK = z.string().brand(`CLIENT#clientId`); +export type PlatformStatesClientPK = z.infer; +export const makePlatformStatesClientPK = ( + clientId: ClientId +): PlatformStatesClientPK => `CLIENT#${clientId}` as PlatformStatesClientPK; + const PlatformStatesBaseEntry = z.object({ - PK: z.string(), state: ItemState, version: z.number(), updatedAt: z.string().datetime(), @@ -33,6 +80,7 @@ const PlatformStatesBaseEntry = z.object({ type PlatformStatesBaseEntry = z.infer; export const PlatformStatesCatalogEntry = PlatformStatesBaseEntry.extend({ + PK: PlatformStatesEServiceDescriptorPK, descriptorAudience: z.string(), }); export type PlatformStatesCatalogEntry = z.infer< @@ -40,6 +88,7 @@ export type PlatformStatesCatalogEntry = z.infer< >; export const PlatformStatesPurposeEntry = PlatformStatesBaseEntry.extend({ + PK: PlatformStatesPurposePK, purposeVersionId: PurposeVersionId, purposeEserviceId: EServiceId, purposeConsumerId: TenantId, @@ -49,6 +98,7 @@ export type PlatformStatesPurposeEntry = z.infer< >; export const PlatformStatesAgreementEntry = PlatformStatesBaseEntry.extend({ + PK: PlatformStatesAgreementPK, GSIPK_consumerId_eserviceId: z.string(), GSISK_agreementTimestamp: z.string().datetime(), agreementDescriptorId: z.string(), @@ -58,6 +108,7 @@ export type PlatformStatesAgreementEntry = z.infer< >; export const PlatformStatesClientEntry = PlatformStatesBaseEntry.extend({ + PK: PlatformStatesClientPK, clientPurposesIds: z.array(PurposeId), }); export type PlatformStatesClientEntry = z.infer< From 82ec49dce867db8f28bdfabfa4d276ad67fe78c5 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Fri, 30 Aug 2024 11:59:18 +0200 Subject: [PATCH 043/241] Improvements --- .../src/consumerServiceV2.ts | 35 +++++++----- .../catalog-platformstate-writer/src/utils.ts | 1 + ...logPlatformstateWriter.integration.test.ts | 56 ++++++++----------- .../test/utils.test.ts | 16 ++++++ packages/commons-test/src/testUtils.ts | 26 +++++++++ 5 files changed, 85 insertions(+), 49 deletions(-) create mode 100644 packages/catalog-platformstate-writer/test/utils.test.ts diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index beca6d711e..9ab0c98f3e 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -1,13 +1,14 @@ import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; import { + DescriptorId, descriptorState, - DescriptorState, EServiceEventEnvelopeV2, - fromEServiceDescriptorStateV2, fromEServiceV2, genericInternalError, itemState, + makePlatformStatesEServiceDescriptorPK, PlatformStatesCatalogEntry, + unsafeBrandId, } from "pagopa-interop-models"; import { match } from "ts-pattern"; import { @@ -26,13 +27,15 @@ export async function handleMessageV2( await match(message) .with({ type: "EServiceDescriptorPublished" }, async (msg) => { const descriptorId = msg.data.descriptorId; - const eservice = msg.data.eservice; - if (!eservice) { + const eserviceV2 = msg.data.eservice; + if (!eserviceV2) { throw genericInternalError( `EService not found in message data for event ${msg.type}` ); } + const eservice = fromEServiceV2(eserviceV2); + const descriptor = eservice.descriptors.find( (d) => d.id === descriptorId ); @@ -41,13 +44,9 @@ export async function handleMessageV2( `Unable to find descriptor with id ${descriptorId}` ); } - const descriptorState: DescriptorState = fromEServiceDescriptorStateV2( - descriptor.state - ); const catalogEntry: PlatformStatesCatalogEntry = { - // TODO: change with the PK type - PK: `ESERVICEDESCRIPTOR#${eservice.id}#${descriptorId}`, - state: descriptorStateToClientState(descriptorState), + PK: makePlatformStatesEServiceDescriptorPK(eservice.id, descriptor.id), + state: descriptorStateToClientState(descriptor.state), descriptorAudience: descriptor.audience[0], version: msg.version, updatedAt: new Date().toISOString(), @@ -70,8 +69,10 @@ export async function handleMessageV2( const eservice = fromEServiceV2(eserviceV2); const descriptorId = msg.data.descriptorId; - // TODO: change with the PK type - const primaryKey = `ESERVICEDESCRIPTOR#${eservice.id}#${descriptorId}`; + const primaryKey = makePlatformStatesEServiceDescriptorPK( + eservice.id, + unsafeBrandId(descriptorId) + ); // TODO: remove read? const catalogEntry = await readCatalogEntry(primaryKey, dynamoDBClient); @@ -115,14 +116,18 @@ export async function handleMessageV2( } ) .with({ type: "EServiceDescriptorArchived" }, async (msg) => { - const eservice = msg.data.eservice; - if (!eservice) { + const eserviceV2 = msg.data.eservice; + if (!eserviceV2) { throw genericInternalError( `EService not found in message data for event ${msg.type}` ); } + const eservice = fromEServiceV2(eserviceV2); - const primaryKey = `ESERVICEDESCRIPTOR#${eservice.id}#${msg.data.descriptorId}`; + const primaryKey = makePlatformStatesEServiceDescriptorPK( + eservice.id, + unsafeBrandId(msg.data.descriptorId) + ); await deleteCatalogEntry(primaryKey, dynamoDBClient); // token-generation-states diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index 5dca2b6177..bcdd87bb18 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import { vi } from "vitest"; import { descriptorState, diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 5ba548106a..4ac0c21614 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import { afterAll, afterEach, @@ -18,7 +19,6 @@ import { EServiceDescriptorSuspendedV2, EServiceEventEnvelope, EServiceId, - ItemState, PlatformStatesCatalogEntry, PurposeId, TenantId, @@ -27,6 +27,7 @@ import { descriptorState, generateId, itemState, + makePlatformStatesEServiceDescriptorPK, toEServiceV2, } from "pagopa-interop-models"; import { @@ -40,6 +41,7 @@ import { getMockDescriptor, getMockEService, getMockDocument, + getMockTokenStatesClientPurposeEntry, } from "pagopa-interop-commons-test"; import { readCatalogEntry, @@ -173,7 +175,10 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - const catalogEntryPrimaryKey = `ESERVICEDESCRIPTOR#${eservice.id}#${publishedDescriptor.id}`; + const catalogEntryPrimaryKey = makePlatformStatesEServiceDescriptorPK( + eservice.id, + publishedDescriptor.id + ); const previousStateEntry: PlatformStatesCatalogEntry = { PK: catalogEntryPrimaryKey, state: itemState.inactive, @@ -189,46 +194,20 @@ describe("database test", async () => { const eserviceId_descriptorId = `${eservice.id}#${publishedDescriptor.id}`; const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { - PK: tokenStateEntryPK1, - descriptorState: itemState.inactive, + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.active, descriptorAudience: publishedDescriptor.audience[0], - updatedAt: new Date().toISOString(), - consumerId: generateId(), - agreementId: generateId(), - purposeVersionId: generateId(), - GSIPK_consumerId_eserviceId: `${generateId()}#${generateId()}`, - clientKind: clientKind.consumer, - publicKey: "PEM", - GSIPK_clientId: generateId(), - GSIPK_kid: "KID", - GSIPK_clientId_purposeId: `${generateId()}#${generateId()}`, - agreementState: "ACTIVE", GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - GSIPK_purposeId: generateId(), - purposeState: itemState.inactive, }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); const tokenStateEntryPK2 = `CLIENTKID#${generateId()}#${generateId()}`; const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { - PK: tokenStateEntryPK2, + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), descriptorState: itemState.active, descriptorAudience: publishedDescriptor.audience[0], - updatedAt: new Date().toISOString(), - consumerId: generateId(), - agreementId: generateId(), - purposeVersionId: generateId(), - GSIPK_consumerId_eserviceId: `${generateId()}#${generateId()}`, - clientKind: clientKind.consumer, - publicKey: "PEM", - GSIPK_clientId: generateId(), - GSIPK_kid: "KID", - GSIPK_clientId_purposeId: `${generateId()}#${generateId()}`, - agreementState: "ACTIVE", GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - GSIPK_purposeId: generateId(), - purposeState: itemState.inactive, }; await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); @@ -313,7 +292,10 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - const primaryKey = `ESERVICEDESCRIPTOR#${updatedEService.id}#${publishedDescriptor.id}`; + const primaryKey = makePlatformStatesEServiceDescriptorPK( + updatedEService.id, + publishedDescriptor.id + ); const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, state: itemState.inactive, @@ -434,7 +416,10 @@ describe("database test", async () => { }; await handleMessageV2(message, dynamoDBClient); - const primaryKey = `ESERVICEDESCRIPTOR#${updatedEService.id}#${publishedDescriptor.id}`; + const primaryKey = makePlatformStatesEServiceDescriptorPK( + updatedEService.id, + publishedDescriptor.id + ); const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); const expectedEntry: PlatformStatesCatalogEntry = { PK: primaryKey, @@ -482,7 +467,10 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - const primaryKey = `ESERVICEDESCRIPTOR#${updatedEService.id}#${publishedDescriptor.id}`; + const primaryKey = makePlatformStatesEServiceDescriptorPK( + updatedEService.id, + publishedDescriptor.id + ); const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, state: itemState.active, diff --git a/packages/catalog-platformstate-writer/test/utils.test.ts b/packages/catalog-platformstate-writer/test/utils.test.ts new file mode 100644 index 0000000000..23731d5694 --- /dev/null +++ b/packages/catalog-platformstate-writer/test/utils.test.ts @@ -0,0 +1,16 @@ +import { + DescriptorId, + EServiceId, + generateId, + makePlatformStatesEServiceDescriptorPK, +} from "pagopa-interop-models"; +import { describe, expect, it } from "vitest"; + +describe("test", () => { + it("makePlatformStatesEServiceDescriptorPK", () => { + const eserviceId = generateId(); + const descriptorId = generateId(); + const PK = makePlatformStatesEServiceDescriptorPK(eserviceId, descriptorId); + expect(PK).toEqual(`ESERVICEDESCRIPTOR#${eserviceId}#${descriptorId}`); + }); +}); diff --git a/packages/commons-test/src/testUtils.ts b/packages/commons-test/src/testUtils.ts index 1759d8c7cf..d13f14fca4 100644 --- a/packages/commons-test/src/testUtils.ts +++ b/packages/commons-test/src/testUtils.ts @@ -34,6 +34,10 @@ import { Key, technology, AttributeKind, + itemState, + ClientId, + PurposeId, + TokenGenerationStatesClientPurposeEntry, } from "pagopa-interop-models"; import { AuthData } from "pagopa-interop-commons"; import { z } from "zod"; @@ -286,3 +290,25 @@ export const getMockAuthData = (organizationId?: TenantId): AuthData => ({ }, selfcareId: generateId(), }); + +export const getMockTokenStatesClientPurposeEntry = + // TODO: change string with tokenStateEntryPK type + (tokenStateEntryPK?: string): TokenGenerationStatesClientPurposeEntry => ({ + PK: tokenStateEntryPK || generateId(), + descriptorState: itemState.inactive, + descriptorAudience: "pagopa.it", + updatedAt: new Date().toISOString(), + consumerId: generateId(), + agreementId: generateId(), + purposeVersionId: generateId(), + GSIPK_consumerId_eserviceId: `${generateId()}#${generateId()}`, + clientKind: clientKind.consumer, + publicKey: "PEM", + GSIPK_clientId: generateId(), + GSIPK_kid: "KID", + GSIPK_clientId_purposeId: `${generateId()}#${generateId()}`, + agreementState: "ACTIVE", + GSIPK_eserviceId_descriptorId: `${generateId()}#${generateId()}`, + GSIPK_purposeId: generateId(), + purposeState: itemState.inactive, + }); From eae0e7c0a76ef0f36def357843cfb62e5a4c408d Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Fri, 30 Aug 2024 12:57:31 +0200 Subject: [PATCH 044/241] Add version check + tests --- .../src/consumerServiceV2.ts | 22 ++++++- ...logPlatformstateWriter.integration.test.ts | 58 ++++++++++++++++++- 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index 9ab0c98f3e..a3e6686143 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -44,8 +44,22 @@ export async function handleMessageV2( `Unable to find descriptor with id ${descriptorId}` ); } + const primaryKey = makePlatformStatesEServiceDescriptorPK( + eservice.id, + descriptor.id + ); + const existingCatalogEntry = await readCatalogEntry( + primaryKey, + dynamoDBClient + ); + + // Stops processing if the message is older than the catalog entry + if (existingCatalogEntry && existingCatalogEntry.version > msg.version) { + return; + } + const catalogEntry: PlatformStatesCatalogEntry = { - PK: makePlatformStatesEServiceDescriptorPK(eservice.id, descriptor.id), + PK: primaryKey, state: descriptorStateToClientState(descriptor.state), descriptorAudience: descriptor.audience[0], version: msg.version, @@ -73,7 +87,6 @@ export async function handleMessageV2( eservice.id, unsafeBrandId(descriptorId) ); - // TODO: remove read? const catalogEntry = await readCatalogEntry(primaryKey, dynamoDBClient); if (!catalogEntry) { @@ -81,6 +94,11 @@ export async function handleMessageV2( `Unable to find catalog entry with PK ${primaryKey}` ); } else { + // Stops processing if the message is older than the catalog entry + if (catalogEntry.version > msg.version) { + return; + } + const updatedCatalogEntry: PlatformStatesCatalogEntry = { ...catalogEntry, state: diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 4ac0c21614..dc8df274d8 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -137,7 +137,7 @@ describe("database test", async () => { describe("Events V2", async () => { const mockEService = getMockEService(); - it.only("EServiceDescriptorActivated", async () => { + it("EServiceDescriptorActivated", async () => { const suspendedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], @@ -248,6 +248,7 @@ describe("database test", async () => { // TODO: this works, but arrayContaining must have the exact objects // expect.arrayContaining([expectedTokenStateEntry2, expectedTokenStateEntry2]) also passes the test + expect(retrievedTokenStateEntries).toHaveLength(2); expect(retrievedTokenStateEntries).toEqual( expect.arrayContaining([ expectedTokenStateEntry2, @@ -431,6 +432,61 @@ describe("database test", async () => { expect(retrievedEntry).toEqual(expectedEntry); }); + // TODO: add test with incoming version 1 and previous entry version 1? + it.only("EServiceDescriptorPublished - no operation if entry already exists. Incoming has version 1; previous entry has version 2", async () => { + const draftDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.draft, + }; + const eservice: EService = { + ...mockEService, + descriptors: [draftDescriptor], + }; + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const publishedDescriptor: Descriptor = { + ...draftDescriptor, + publishedAt: new Date(), + state: descriptorState.published, + }; + const updatedEService: EService = { + ...eservice, + descriptors: [publishedDescriptor], + }; + const payload: EServiceDescriptorPublishedV2 = { + eservice: toEServiceV2(updatedEService), + descriptorId: publishedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 1, + type: "EServiceDescriptorPublished", + event_version: 2, + data: payload, + log_date: new Date(), + }; + + const primaryKey = makePlatformStatesEServiceDescriptorPK( + updatedEService.id, + publishedDescriptor.id + ); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + version: 2, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); + await handleMessageV2(message, dynamoDBClient); + + const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + expect(retrievedEntry).toEqual(previousStateEntry); + }); + it("EServiceDescriptorSuspended", async () => { const publishedDescriptor: Descriptor = { ...getMockDescriptor(), From dff763f0dc26d4ab43f8e853d068753d6e75337b Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Fri, 30 Aug 2024 14:23:10 +0200 Subject: [PATCH 045/241] Add makePlatformStatesEServiceDescriptorPK type --- .../platform-states-entry.ts | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/models/src/token-generation-readmodel/platform-states-entry.ts b/packages/models/src/token-generation-readmodel/platform-states-entry.ts index 81fa5cd4ac..2ad71dd8ab 100644 --- a/packages/models/src/token-generation-readmodel/platform-states-entry.ts +++ b/packages/models/src/token-generation-readmodel/platform-states-entry.ts @@ -44,10 +44,13 @@ export type PlatformStatesEServiceDescriptorPK = z.infer< typeof PlatformStatesEServiceDescriptorPK >; -export const makePlatformStatesEServiceDescriptorPK = ( - eserviceId: EServiceId, - descriptorId: DescriptorId -): PlatformStatesEServiceDescriptorPK => +export const makePlatformStatesEServiceDescriptorPK = ({ + eserviceId, + descriptorId, +}: { + eserviceId: EServiceId; + descriptorId: DescriptorId; +}): PlatformStatesEServiceDescriptorPK => `ESERVICEDESCRIPTOR#${eserviceId}#${descriptorId}` as PlatformStatesEServiceDescriptorPK; export const PlatformStatesAgreementPK = z .string() @@ -72,6 +75,21 @@ export const makePlatformStatesClientPK = ( clientId: ClientId ): PlatformStatesClientPK => `CLIENT#${clientId}` as PlatformStatesClientPK; +export const PlatformStatesGSIPKConsumerIdEServiceId = z + .string() + .brand(`tenantId#eserviceId`); +export type PlatformStatesGSIPKConsumerIdEServiceId = z.infer< + typeof PlatformStatesGSIPKConsumerIdEServiceId +>; +export const makePlatformStatesGSIPKConsumerIdEServiceId = ({ + consumerId, + eserviceId, +}: { + consumerId: TenantId; + eserviceId: EServiceId; +}): PlatformStatesGSIPKConsumerIdEServiceId => + `${consumerId}#${eserviceId}` as PlatformStatesGSIPKConsumerIdEServiceId; + const PlatformStatesBaseEntry = z.object({ state: ItemState, version: z.number(), From 81a48cb1899a7ba77b46c50c7583d5c48391316c Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 30 Aug 2024 14:28:06 +0200 Subject: [PATCH 046/241] Fix sample test --- packages/catalog-platformstate-writer/test/sample.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/catalog-platformstate-writer/test/sample.test.ts b/packages/catalog-platformstate-writer/test/sample.test.ts index a0769ee33d..bbd49c4daa 100644 --- a/packages/catalog-platformstate-writer/test/sample.test.ts +++ b/packages/catalog-platformstate-writer/test/sample.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -describe(() => { +describe("test", () => { it("sample", () => { expect(1).toBe(1); }); From 0461f50af18255e16d1b787f6ba8562bd0fe53a1 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 30 Aug 2024 14:37:32 +0200 Subject: [PATCH 047/241] Fix --- .../src/consumerServiceV2.ts | 24 +++++------ ...logPlatformstateWriter.integration.test.ts | 40 +++++++++---------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index a3e6686143..a63dc95998 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -44,10 +44,10 @@ export async function handleMessageV2( `Unable to find descriptor with id ${descriptorId}` ); } - const primaryKey = makePlatformStatesEServiceDescriptorPK( - eservice.id, - descriptor.id - ); + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: descriptor.id, + }); const existingCatalogEntry = await readCatalogEntry( primaryKey, dynamoDBClient @@ -83,10 +83,10 @@ export async function handleMessageV2( const eservice = fromEServiceV2(eserviceV2); const descriptorId = msg.data.descriptorId; - const primaryKey = makePlatformStatesEServiceDescriptorPK( - eservice.id, - unsafeBrandId(descriptorId) - ); + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: unsafeBrandId(descriptorId), + }); const catalogEntry = await readCatalogEntry(primaryKey, dynamoDBClient); if (!catalogEntry) { @@ -142,10 +142,10 @@ export async function handleMessageV2( } const eservice = fromEServiceV2(eserviceV2); - const primaryKey = makePlatformStatesEServiceDescriptorPK( - eservice.id, - unsafeBrandId(msg.data.descriptorId) - ); + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: unsafeBrandId(msg.data.descriptorId), + }); await deleteCatalogEntry(primaryKey, dynamoDBClient); // token-generation-states diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index dc8df274d8..0a987b5edb 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -175,10 +175,10 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - const catalogEntryPrimaryKey = makePlatformStatesEServiceDescriptorPK( - eservice.id, - publishedDescriptor.id - ); + const catalogEntryPrimaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); const previousStateEntry: PlatformStatesCatalogEntry = { PK: catalogEntryPrimaryKey, state: itemState.inactive, @@ -293,10 +293,10 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - const primaryKey = makePlatformStatesEServiceDescriptorPK( - updatedEService.id, - publishedDescriptor.id - ); + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: updatedEService.id, + descriptorId: publishedDescriptor.id, + }); const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, state: itemState.inactive, @@ -417,10 +417,10 @@ describe("database test", async () => { }; await handleMessageV2(message, dynamoDBClient); - const primaryKey = makePlatformStatesEServiceDescriptorPK( - updatedEService.id, - publishedDescriptor.id - ); + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: updatedEService.id, + descriptorId: publishedDescriptor.id, + }); const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); const expectedEntry: PlatformStatesCatalogEntry = { PK: primaryKey, @@ -469,10 +469,10 @@ describe("database test", async () => { log_date: new Date(), }; - const primaryKey = makePlatformStatesEServiceDescriptorPK( - updatedEService.id, - publishedDescriptor.id - ); + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: updatedEService.id, + descriptorId: publishedDescriptor.id, + }); const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, state: itemState.active, @@ -523,10 +523,10 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - const primaryKey = makePlatformStatesEServiceDescriptorPK( - updatedEService.id, - publishedDescriptor.id - ); + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: updatedEService.id, + descriptorId: publishedDescriptor.id, + }); const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, state: itemState.active, From 78c7037b23dd7586e9bd2d72908abb731499224b Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 30 Aug 2024 14:40:43 +0200 Subject: [PATCH 048/241] Fix --- ...logPlatformstateWriter.integration.test.ts | 24 +++++++++++++------ .../test/utils.test.ts | 5 +++- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 0a987b5edb..f52bc59768 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -1,4 +1,5 @@ /* eslint-disable no-console */ +import { fail } from "assert"; import { afterAll, afterEach, @@ -51,23 +52,29 @@ import { writeTokenStateEntry, } from "../src/utils.js"; import { handleMessageV2 } from "../src/consumerServiceV2.js"; - import { config } from "./utils.js"; describe("database test", async () => { + if (!config) { + fail(); + } const dynamoDBClient = new DynamoDBClient({ credentials: { accessKeyId: "key", secretAccessKey: "secret" }, region: "eu-central-1", // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - endpoint: `http://${config!.tokenGenerationReadModelDbHost}:${ + endpoint: `http://${config.tokenGenerationReadModelDbHost}:${ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - config!.tokenGenerationReadModelDbPort + config.tokenGenerationReadModelDbPort }`, }); beforeEach(async () => { + if (!config) { + // to do: why is this needed? + fail(); + } const platformTableDefinition: CreateTableInput = { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - TableName: config!.tokenGenerationReadModelTableNamePlatform, + TableName: config.tokenGenerationReadModelTableNamePlatform, AttributeDefinitions: [{ AttributeName: "PK", AttributeType: "S" }], KeySchema: [{ AttributeName: "PK", KeyType: "HASH" }], BillingMode: "PAY_PER_REQUEST", @@ -77,7 +84,7 @@ describe("database test", async () => { const tokenGenerationTableDefinition: CreateTableInput = { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - TableName: config!.tokenGenerationReadModelTableNameTokenGeneration, + TableName: config.tokenGenerationReadModelTableNameTokenGeneration, AttributeDefinitions: [ { AttributeName: "PK", AttributeType: "S" }, { AttributeName: "GSIPK_eserviceId_descriptorId", AttributeType: "S" }, @@ -113,13 +120,16 @@ describe("database test", async () => { // console.log(tablesResult.TableNames); }); afterEach(async () => { + if (!config) { + fail(); + } const tableToDelete1: DeleteTableInput = { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - TableName: config!.tokenGenerationReadModelTableNamePlatform, + TableName: config.tokenGenerationReadModelTableNamePlatform, }; const tableToDelete2: DeleteTableInput = { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - TableName: config!.tokenGenerationReadModelTableNameTokenGeneration, + TableName: config.tokenGenerationReadModelTableNameTokenGeneration, }; const command1 = new DeleteTableCommand(tableToDelete1); await dynamoDBClient.send(command1); diff --git a/packages/catalog-platformstate-writer/test/utils.test.ts b/packages/catalog-platformstate-writer/test/utils.test.ts index 23731d5694..e5eb3d6e7e 100644 --- a/packages/catalog-platformstate-writer/test/utils.test.ts +++ b/packages/catalog-platformstate-writer/test/utils.test.ts @@ -10,7 +10,10 @@ describe("test", () => { it("makePlatformStatesEServiceDescriptorPK", () => { const eserviceId = generateId(); const descriptorId = generateId(); - const PK = makePlatformStatesEServiceDescriptorPK(eserviceId, descriptorId); + const PK = makePlatformStatesEServiceDescriptorPK({ + eserviceId, + descriptorId, + }); expect(PK).toEqual(`ESERVICEDESCRIPTOR#${eserviceId}#${descriptorId}`); }); }); From 6dffe6df731f26e3a641361dc8ea43438f4f0714 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 30 Aug 2024 14:49:40 +0200 Subject: [PATCH 049/241] Renaming --- .../src/consumerServiceV2.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index a63dc95998..a3e9c8ee6e 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -151,13 +151,14 @@ export async function handleMessageV2( // token-generation-states const descriptorId = msg.data.descriptorId; const eserviceId_descriptorId = `${eservice.id}#${descriptorId}`; - const result = await readTokenStateEntriesByEserviceIdAndDescriptorId( - eserviceId_descriptorId, - dynamoDBClient - ); + const entriesToUpdate = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); - if (result) { - for (const entry of result) { + if (entriesToUpdate) { + for (const entry of entriesToUpdate) { await updateDescriptorState( dynamoDBClient, entry.PK, From c981b28869de75919548025de57948da4510620b Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 30 Aug 2024 14:49:45 +0200 Subject: [PATCH 050/241] Improve test --- ...catalogPlatformstateWriter.integration.test.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index f52bc59768..a580edbf61 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -320,7 +320,7 @@ describe("database test", async () => { const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { PK: "TO DO client kid purpose 1", - descriptorState: itemState.inactive, + descriptorState: itemState.active, descriptorAudience: publishedDescriptor.audience[0], updatedAt: new Date().toISOString(), consumerId: generateId(), @@ -342,7 +342,7 @@ describe("database test", async () => { const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { PK: "TO DO client kid purpose 2", - descriptorState: itemState.inactive, + descriptorState: itemState.active, descriptorAudience: publishedDescriptor.audience[0], updatedAt: new Date().toISOString(), consumerId: generateId(), @@ -383,11 +383,14 @@ describe("database test", async () => { { ...previousTokenStateEntry2, descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), }; - expect(retrievedTokenStateEntries).toEqual([ - expectedTokenStateEntry2, - expectedTokenStateEntry1, // To do: understand how these are sorted - ]); + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry1, + expectedTokenStateEntry2, + ]) + ); }); it("EServiceDescriptorPublished", async () => { From ff731bfe2e801f1ec360ddc4c821f82cae9045f0 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 30 Aug 2024 14:54:27 +0200 Subject: [PATCH 051/241] Improve test --- ...logPlatformstateWriter.integration.test.ts | 55 ++++++++++++++++++- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index a580edbf61..7c5adc2866 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -205,7 +205,7 @@ describe("database test", async () => { const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), - descriptorState: itemState.active, + descriptorState: itemState.inactive, descriptorAudience: publishedDescriptor.audience[0], GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; @@ -215,7 +215,7 @@ describe("database test", async () => { const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), - descriptorState: itemState.active, + descriptorState: itemState.inactive, descriptorAudience: publishedDescriptor.audience[0], GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; @@ -548,6 +548,30 @@ describe("database test", async () => { updatedAt: new Date().toISOString(), }; await writeCatalogEntry(previousStateEntry, dynamoDBClient); + + // token-generation-states + // TODO: replace last generateId() with kid + const tokenStateEntryPK1 = `CLIENTKID#${generateId()}#${generateId()}`; + const eserviceId_descriptorId = `${eservice.id}#${publishedDescriptor.id}`; + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = `CLIENTKID#${generateId()}#${generateId()}`; + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + await handleMessageV2(message, dynamoDBClient); const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); @@ -558,6 +582,33 @@ describe("database test", async () => { updatedAt: new Date().toISOString(), }; expect(retrievedEntry).toEqual(expectedEntry); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + + expect(retrievedTokenStateEntries).toHaveLength(2); + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry2, + expectedTokenStateEntry1, + ]) + ); }); }); }); From b3eb02aada57deac650b8306e5f62a632de7ffcb Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 30 Aug 2024 15:01:11 +0200 Subject: [PATCH 052/241] Remove only --- .../test/catalogPlatformstateWriter.integration.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 7c5adc2866..19780c6b01 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -446,7 +446,7 @@ describe("database test", async () => { }); // TODO: add test with incoming version 1 and previous entry version 1? - it.only("EServiceDescriptorPublished - no operation if entry already exists. Incoming has version 1; previous entry has version 2", async () => { + it("EServiceDescriptorPublished - no operation if entry already exists. Incoming has version 1; previous entry has version 2", async () => { const draftDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], From 5f0d7b00b65fa44dc4f84fb7266f38d3b234cade Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 30 Aug 2024 15:19:19 +0200 Subject: [PATCH 053/241] Improve keys and GSI types --- .../dynamoDB-keys.ts | 120 ++++++++++++++++++ .../platform-states-entry.ts | 65 ++-------- .../token-generation-states-entry.ts | 37 ++++-- 3 files changed, 154 insertions(+), 68 deletions(-) create mode 100644 packages/models/src/token-generation-readmodel/dynamoDB-keys.ts diff --git a/packages/models/src/token-generation-readmodel/dynamoDB-keys.ts b/packages/models/src/token-generation-readmodel/dynamoDB-keys.ts new file mode 100644 index 0000000000..292e4b6ebc --- /dev/null +++ b/packages/models/src/token-generation-readmodel/dynamoDB-keys.ts @@ -0,0 +1,120 @@ +import { z } from "zod"; +import { + EServiceId, + DescriptorId, + AgreementId, + PurposeId, + ClientId, + TenantId, +} from "../brandedIds.js"; + +export const PlatformStatesEServiceDescriptorPK = z + .string() + .brand(`ESERVICEDESCRIPTOR#eServiceId#descriptorId`); +export type PlatformStatesEServiceDescriptorPK = z.infer< + typeof PlatformStatesEServiceDescriptorPK +>; +export const makePlatformStatesEServiceDescriptorPK = ({ + eserviceId, + descriptorId, +}: { + eserviceId: EServiceId; + descriptorId: DescriptorId; +}): PlatformStatesEServiceDescriptorPK => + `ESERVICEDESCRIPTOR#${eserviceId}#${descriptorId}` as PlatformStatesEServiceDescriptorPK; + +export const PlatformStatesAgreementPK = z + .string() + .brand(`AGREEMENT#agreementId`); +export type PlatformStatesAgreementPK = z.infer< + typeof PlatformStatesAgreementPK +>; +export const makePlatformStatesAgreementPK = ( + agreementId: AgreementId +): PlatformStatesAgreementPK => + `AGREEMENT#${agreementId}` as PlatformStatesAgreementPK; + +export const PlatformStatesPurposePK = z.string().brand(`PURPOSE#purposeId`); +export type PlatformStatesPurposePK = z.infer; +export const makePlatformStatesPurposePK = ( + purposeId: PurposeId +): PlatformStatesPurposePK => `PURPOSE#${purposeId}` as PlatformStatesPurposePK; + +export const PlatformStatesClientPK = z.string().brand(`CLIENT#clientId`); +export type PlatformStatesClientPK = z.infer; +export const makePlatformStatesClientPK = ( + clientId: ClientId +): PlatformStatesClientPK => `CLIENT#${clientId}` as PlatformStatesClientPK; + +export const GSIPKConsumerIdEServiceId = z + .string() + .brand(`tenantId#eserviceId`); +export type GSIPKConsumerIdEServiceId = z.infer< + typeof GSIPKConsumerIdEServiceId +>; +export const makeGSIPKConsumerIdEServiceId = ({ + consumerId, + eserviceId, +}: { + consumerId: TenantId; + eserviceId: EServiceId; +}): GSIPKConsumerIdEServiceId => + `${consumerId}#${eserviceId}` as GSIPKConsumerIdEServiceId; + +export const TokenGenerationStatesClientKidPurposePK = z + .string() + .brand(`ESERVICEDESCRIPTOR#eServiceId#descriptorId`); +export type TokenGenerationStatesClientKidPurposePK = z.infer< + typeof TokenGenerationStatesClientKidPurposePK +>; +export const makeTokenGenerationStatesClientKidPurposePK = ({ + clientId, + kid, + purposeId, +}: { + clientId: ClientId; + kid: string; + purposeId: PurposeId; +}): TokenGenerationStatesClientKidPurposePK => + `CLIENTKIDPURPOSE#${clientId}#${kid}#${purposeId}` as TokenGenerationStatesClientKidPurposePK; + +export const TokenGenerationStatesClientKidPK = z + .string() + .brand(`ESERVICEDESCRIPTOR#eServiceId#descriptorId`); +export type TokenGenerationStatesClientKidPK = z.infer< + typeof TokenGenerationStatesClientKidPK +>; +export const makeTokenGenerationStatesClientKidPK = ({ + clientId, + kid, +}: { + clientId: ClientId; + kid: string; +}): TokenGenerationStatesClientKidPK => + `CLIENTKIDPURPOSE#${clientId}#${kid}` as TokenGenerationStatesClientKidPK; + +export const GSIPKEServiceIdDescriptorId = z + .string() + .brand(`eserviceId#descriptorId`); +export type GSIPKEServiceIdDescriptorId = z.infer< + typeof GSIPKEServiceIdDescriptorId +>; +export const makeGSIPKEServiceIdDescriptorId = ({ + eserviceId, + descriptorId, +}: { + eserviceId: EServiceId; + descriptorId: DescriptorId; +}): GSIPKEServiceIdDescriptorId => + `${eserviceId}#${descriptorId}` as GSIPKEServiceIdDescriptorId; + +export const GSIPKClientIdPurposeId = z.string().brand(`clientId#purposeId`); +export type GSIPKClientIdPurposeId = z.infer; +export const makeGSIPKClientIdPurposeId = ({ + clientId, + purposeId, +}: { + clientId: ClientId; + purposeId: PurposeId; +}): GSIPKClientIdPurposeId => + `${clientId}#${purposeId}` as GSIPKClientIdPurposeId; diff --git a/packages/models/src/token-generation-readmodel/platform-states-entry.ts b/packages/models/src/token-generation-readmodel/platform-states-entry.ts index 2ad71dd8ab..ed80bb0886 100644 --- a/packages/models/src/token-generation-readmodel/platform-states-entry.ts +++ b/packages/models/src/token-generation-readmodel/platform-states-entry.ts @@ -1,13 +1,17 @@ import { z } from "zod"; import { - AgreementId, - ClientId, - DescriptorId, EServiceId, PurposeId, PurposeVersionId, TenantId, } from "../brandedIds.js"; +import { + PlatformStatesEServiceDescriptorPK, + PlatformStatesPurposePK, + PlatformStatesAgreementPK, + GSIPKConsumerIdEServiceId, + PlatformStatesClientPK, +} from "./dynamoDB-keys.js"; export const itemState = { active: "ACTIVE", @@ -37,59 +41,6 @@ const f: PlatformStatesEServiceDescriptorPK = `test#test#test`; // WRONG We don't check the structure of the ids because they are treated as strings */ -export const PlatformStatesEServiceDescriptorPK = z - .string() - .brand(`ESERVICEDESCRIPTOR#eServiceId#descriptorId`); -export type PlatformStatesEServiceDescriptorPK = z.infer< - typeof PlatformStatesEServiceDescriptorPK ->; - -export const makePlatformStatesEServiceDescriptorPK = ({ - eserviceId, - descriptorId, -}: { - eserviceId: EServiceId; - descriptorId: DescriptorId; -}): PlatformStatesEServiceDescriptorPK => - `ESERVICEDESCRIPTOR#${eserviceId}#${descriptorId}` as PlatformStatesEServiceDescriptorPK; -export const PlatformStatesAgreementPK = z - .string() - .brand(`AGREEMENT#agreementId`); -export type PlatformStatesAgreementPK = z.infer< - typeof PlatformStatesAgreementPK ->; -export const makePlatformStatesAgreementPK = ( - agreementId: AgreementId -): PlatformStatesAgreementPK => - `AGREEMENT#${agreementId}` as PlatformStatesAgreementPK; - -export const PlatformStatesPurposePK = z.string().brand(`PURPOSE#purposeId`); -export type PlatformStatesPurposePK = z.infer; -export const makePlatformStatesPurposePK = ( - purposeId: PurposeId -): PlatformStatesPurposePK => `PURPOSE#${purposeId}` as PlatformStatesPurposePK; - -export const PlatformStatesClientPK = z.string().brand(`CLIENT#clientId`); -export type PlatformStatesClientPK = z.infer; -export const makePlatformStatesClientPK = ( - clientId: ClientId -): PlatformStatesClientPK => `CLIENT#${clientId}` as PlatformStatesClientPK; - -export const PlatformStatesGSIPKConsumerIdEServiceId = z - .string() - .brand(`tenantId#eserviceId`); -export type PlatformStatesGSIPKConsumerIdEServiceId = z.infer< - typeof PlatformStatesGSIPKConsumerIdEServiceId ->; -export const makePlatformStatesGSIPKConsumerIdEServiceId = ({ - consumerId, - eserviceId, -}: { - consumerId: TenantId; - eserviceId: EServiceId; -}): PlatformStatesGSIPKConsumerIdEServiceId => - `${consumerId}#${eserviceId}` as PlatformStatesGSIPKConsumerIdEServiceId; - const PlatformStatesBaseEntry = z.object({ state: ItemState, version: z.number(), @@ -117,7 +68,7 @@ export type PlatformStatesPurposeEntry = z.infer< export const PlatformStatesAgreementEntry = PlatformStatesBaseEntry.extend({ PK: PlatformStatesAgreementPK, - GSIPK_consumerId_eserviceId: z.string(), + GSIPK_consumerId_eserviceId: GSIPKConsumerIdEServiceId, GSISK_agreementTimestamp: z.string().datetime(), agreementDescriptorId: z.string(), }); diff --git a/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts b/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts index e914ccfdd6..e35c5f5045 100644 --- a/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts +++ b/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts @@ -1,16 +1,28 @@ import { z } from "zod"; import { ClientKind } from "../authorization/client.js"; -import { AgreementId, PurposeVersionId } from "../brandedIds.js"; +import { + AgreementId, + ClientId, + PurposeId, + PurposeVersionId, + TenantId, +} from "../brandedIds.js"; import { ItemState } from "./platform-states-entry.js"; +import { + TokenGenerationStatesClientKidPurposePK, + TokenGenerationStatesClientKidPK, + GSIPKConsumerIdEServiceId, + GSIPKEServiceIdDescriptorId, + GSIPKClientIdPurposeId, +} from "./dynamoDB-keys.js"; const TokenGenerationStatesBaseEntry = z.object({ - PK: z.string(), - consumerId: z.string(), + consumerId: TenantId, clientKind: ClientKind, publicKey: z.string(), - GSIPK_clientId: z.string(), + GSIPK_clientId: ClientId, GSIPK_kid: z.string(), - GSIPK_clientId_purposeId: z.string(), + GSIPK_clientId_purposeId: GSIPKClientIdPurposeId, updatedAt: z.string().datetime(), }); type TokenGenerationStatesBaseEntry = z.infer< @@ -18,15 +30,15 @@ type TokenGenerationStatesBaseEntry = z.infer< >; export const TokenGenerationStatesClientPurposeEntry = - TokenGenerationStatesBaseEntry && - z.object({ - GSIPK_consumerId_eserviceId: z.string(), + TokenGenerationStatesBaseEntry.extend({ + PK: TokenGenerationStatesClientKidPurposePK, + GSIPK_consumerId_eserviceId: GSIPKConsumerIdEServiceId, agreementId: AgreementId, agreementState: ItemState, - GSIPK_eserviceId_descriptorId: z.string(), + GSIPK_eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, descriptorState: ItemState, descriptorAudience: z.string(), - GSIPK_purposeId: z.string(), + GSIPK_purposeId: PurposeId, purposeState: ItemState, purposeVersionId: PurposeVersionId, }); @@ -34,7 +46,10 @@ export type TokenGenerationStatesClientPurposeEntry = z.infer< typeof TokenGenerationStatesClientPurposeEntry >; -export const TokenGenerationStatesClientEntry = TokenGenerationStatesBaseEntry; +export const TokenGenerationStatesClientEntry = + TokenGenerationStatesBaseEntry.extend({ + PK: TokenGenerationStatesClientKidPK, + }); export type TokenGenerationStatesClientEntry = z.infer< typeof TokenGenerationStatesClientPurposeEntry >; From 1b9bcb103ff314b053518c4906ccc04a836d84b5 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 30 Aug 2024 15:27:51 +0200 Subject: [PATCH 054/241] Export types --- packages/models/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/models/src/index.ts b/packages/models/src/index.ts index 121901ace4..bc0d0405d0 100644 --- a/packages/models/src/index.ts +++ b/packages/models/src/index.ts @@ -54,6 +54,7 @@ export * from "./user/user.js"; export * from "./token-generation-readmodel/platform-states-entry.js"; export * from "./token-generation-readmodel/token-generation-states-entry.js"; +export * from "./token-generation-readmodel/dynamoDB-keys.js"; // Protobuf export * from "./protobuf/protobuf.js"; From 92ae7e45df4d9ac77ec6f311f6b21b1b8fcb5985 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 30 Aug 2024 15:28:18 +0200 Subject: [PATCH 055/241] Update mocks --- packages/commons-test/src/testUtils.ts | 62 +++++++++++++++++--------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/packages/commons-test/src/testUtils.ts b/packages/commons-test/src/testUtils.ts index d13f14fca4..068b9b1d9d 100644 --- a/packages/commons-test/src/testUtils.ts +++ b/packages/commons-test/src/testUtils.ts @@ -38,6 +38,11 @@ import { ClientId, PurposeId, TokenGenerationStatesClientPurposeEntry, + makeGSIPKConsumerIdEServiceId, + makeGSIPKClientIdPurposeId, + makeGSIPKEServiceIdDescriptorId, + TokenGenerationStatesClientKidPurposePK, + makeTokenGenerationStatesClientKidPurposePK, } from "pagopa-interop-models"; import { AuthData } from "pagopa-interop-commons"; import { z } from "zod"; @@ -291,24 +296,39 @@ export const getMockAuthData = (organizationId?: TenantId): AuthData => ({ selfcareId: generateId(), }); -export const getMockTokenStatesClientPurposeEntry = - // TODO: change string with tokenStateEntryPK type - (tokenStateEntryPK?: string): TokenGenerationStatesClientPurposeEntry => ({ - PK: tokenStateEntryPK || generateId(), - descriptorState: itemState.inactive, - descriptorAudience: "pagopa.it", - updatedAt: new Date().toISOString(), - consumerId: generateId(), - agreementId: generateId(), - purposeVersionId: generateId(), - GSIPK_consumerId_eserviceId: `${generateId()}#${generateId()}`, - clientKind: clientKind.consumer, - publicKey: "PEM", - GSIPK_clientId: generateId(), - GSIPK_kid: "KID", - GSIPK_clientId_purposeId: `${generateId()}#${generateId()}`, - agreementState: "ACTIVE", - GSIPK_eserviceId_descriptorId: `${generateId()}#${generateId()}`, - GSIPK_purposeId: generateId(), - purposeState: itemState.inactive, - }); +export const getMockTokenStatesClientPurposeEntry = ( + tokenStateEntryPK?: TokenGenerationStatesClientKidPurposePK +): TokenGenerationStatesClientPurposeEntry => ({ + PK: + tokenStateEntryPK || + makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: generateId(), + purposeId: generateId(), + }), + descriptorState: itemState.inactive, + descriptorAudience: "pagopa.it", + updatedAt: new Date().toISOString(), + consumerId: generateId(), + agreementId: generateId(), + purposeVersionId: generateId(), + GSIPK_consumerId_eserviceId: makeGSIPKConsumerIdEServiceId({ + consumerId: generateId(), + eserviceId: generateId(), + }), + clientKind: clientKind.consumer, + publicKey: "PEM", + GSIPK_clientId: generateId(), + GSIPK_kid: "KID", + GSIPK_clientId_purposeId: makeGSIPKClientIdPurposeId({ + clientId: generateId(), + purposeId: generateId(), + }), + agreementState: "ACTIVE", + GSIPK_eserviceId_descriptorId: makeGSIPKEServiceIdDescriptorId({ + eserviceId: generateId(), + descriptorId: generateId(), + }), + GSIPK_purposeId: generateId(), + purposeState: itemState.inactive, +}); From 8f7425a7f8ed85ae1a8773022a81b443682be427 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 30 Aug 2024 15:37:25 +0200 Subject: [PATCH 056/241] Use new types --- ...logPlatformstateWriter.integration.test.ts | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 19780c6b01..51c9a187cd 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -19,16 +19,14 @@ import { EServiceDescriptorPublishedV2, EServiceDescriptorSuspendedV2, EServiceEventEnvelope, - EServiceId, PlatformStatesCatalogEntry, - PurposeId, - TenantId, TokenGenerationStatesClientPurposeEntry, - clientKind, descriptorState, generateId, itemState, + makeGSIPKEServiceIdDescriptorId, makePlatformStatesEServiceDescriptorPK, + makeTokenGenerationStatesClientKidPK, toEServiceV2, } from "pagopa-interop-models"; import { @@ -199,9 +197,14 @@ describe("database test", async () => { await writeCatalogEntry(previousStateEntry, dynamoDBClient); // token-generation-states - // TODO: replace last generateId() with kid - const tokenStateEntryPK1 = `CLIENTKID#${generateId()}#${generateId()}`; - const eserviceId_descriptorId = `${eservice.id}#${publishedDescriptor.id}`; + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), @@ -211,7 +214,10 @@ describe("database test", async () => { }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - const tokenStateEntryPK2 = `CLIENTKID#${generateId()}#${generateId()}`; + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), @@ -316,48 +322,33 @@ describe("database test", async () => { }; await writeCatalogEntry(previousStateEntry, dynamoDBClient); - const eserviceId_descriptorId = `${eservice.id}#${publishedDescriptor.id}`; + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { - PK: "TO DO client kid purpose 1", + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), descriptorState: itemState.active, descriptorAudience: publishedDescriptor.audience[0], - updatedAt: new Date().toISOString(), - consumerId: generateId(), - agreementId: generateId(), - purposeVersionId: generateId(), - GSIPK_consumerId_eserviceId: `${generateId()}#${generateId()}`, - clientKind: clientKind.consumer, - publicKey: "PEM", - GSIPK_clientId: generateId(), - GSIPK_kid: "KID", - GSIPK_clientId_purposeId: `${generateId()}#${generateId()}`, - agreementState: "ACTIVE", GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - GSIPK_purposeId: generateId(), - purposeState: itemState.inactive, }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { - PK: "TO DO client kid purpose 2", + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), descriptorState: itemState.active, descriptorAudience: publishedDescriptor.audience[0], - updatedAt: new Date().toISOString(), - consumerId: generateId(), - agreementId: generateId(), - purposeVersionId: generateId(), - GSIPK_consumerId_eserviceId: `${generateId()}#${generateId()}`, - clientKind: clientKind.consumer, - publicKey: "PEM", - GSIPK_clientId: generateId(), - GSIPK_kid: "KID", - GSIPK_clientId_purposeId: `${generateId()}#${generateId()}`, - agreementState: "ACTIVE", GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - GSIPK_purposeId: generateId(), - purposeState: itemState.inactive, }; await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); await sleep(1000, mockDate); @@ -551,8 +542,14 @@ describe("database test", async () => { // token-generation-states // TODO: replace last generateId() with kid - const tokenStateEntryPK1 = `CLIENTKID#${generateId()}#${generateId()}`; - const eserviceId_descriptorId = `${eservice.id}#${publishedDescriptor.id}`; + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), @@ -562,7 +559,10 @@ describe("database test", async () => { }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - const tokenStateEntryPK2 = `CLIENTKID#${generateId()}#${generateId()}`; + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), From ad89d1b5c54ca33834d926f0f784567adf7b6b76 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 30 Aug 2024 15:42:54 +0200 Subject: [PATCH 057/241] Fix tests --- ...logPlatformstateWriter.integration.test.ts | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 43bdbaa9ee..220a74ffea 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -182,7 +182,10 @@ describe("database test", async () => { await handleMessageV1(message, dynamoDBClient); await sleep(1000, mockDate); - const primaryKey = `ESERVICEDESCRIPTOR#${eservice.id}#${publishedDescriptor.id}`; + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); const expectedEntry: PlatformStatesCatalogEntry = { PK: primaryKey, @@ -229,7 +232,10 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - const primaryKey = `ESERVICEDESCRIPTOR#${eservice.id}#${publishedDescriptor.id}`; + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, state: itemState.inactive, @@ -283,7 +289,10 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - const primaryKey = `ESERVICEDESCRIPTOR#${eservice.id}#${publishedDescriptor.id}`; + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, state: itemState.active, @@ -338,7 +347,10 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; - const primaryKey = `ESERVICEDESCRIPTOR#${eservice.id}#${publishedDescriptor.id}`; + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, state: itemState.inactive, From 8a50e45ce72c03f90cf871279ab1fbbbdc32719f Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 30 Aug 2024 16:43:04 +0200 Subject: [PATCH 058/241] Update logic --- .../src/consumerServiceV1.ts | 60 ++++++++++++++++--- .../catalog-platformstate-writer/src/utils.ts | 38 +++++++++++- ...logPlatformstateWriter.integration.test.ts | 2 +- 3 files changed, 90 insertions(+), 10 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index 38d4acd734..0084991ffb 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -2,14 +2,19 @@ import { match } from "ts-pattern"; import { descriptorState, EServiceEventEnvelopeV1, + EServiceId, fromDescriptorV1, genericInternalError, + makePlatformStatesEServiceDescriptorPK, PlatformStatesCatalogEntry, + unsafeBrandId, } from "pagopa-interop-models"; import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; import { deleteCatalogEntry, descriptorStateToClientState, + readCatalogEntry, + updateDescriptorStateInPlatformStatesEntry, writeCatalogEntry, } from "./utils.js"; @@ -22,7 +27,7 @@ export async function handleMessageV1( // EServiceDescriptorActivated,EServiceDescriptorSuspended -> EServiceDescriptorUpdated // EServiceDescriptorArchived -> EServiceDescriptorUpdated .with({ type: "EServiceDescriptorUpdated" }, async (msg) => { - const eserviceId = msg.data.eserviceId; + const eserviceId = unsafeBrandId(msg.data.eserviceId); const descriptorV1 = msg.data.eserviceDescriptor; if (!descriptorV1) { throw genericInternalError( @@ -32,21 +37,60 @@ export async function handleMessageV1( const descriptor = fromDescriptorV1(descriptorV1); match(descriptor.state) - .with( - descriptorState.published, - descriptorState.suspended, - async () => { + .with(descriptorState.published, async () => { + // steps: + // capire se siamo in (draft -> published) o (suspened -> published) + // fare query su platform states e vedere se c'è + // se non c'è sono in draft, e continuo questa esecuzione + // se c'è (presumibilmente come inactive) allora era suspended e sono nel caso sotto (sospensione e riattivazione hanno stesso handler) + const eserviceDescriptorPK = makePlatformStatesEServiceDescriptorPK({ + eserviceId, + descriptorId: descriptor.id, + }); + const existingCatalogEntry = await readCatalogEntry( + eserviceDescriptorPK, + dynamoDBClient + ); + + if (!existingCatalogEntry) { + // the descriptor was draft so there was not an entry in platform-states const catalogEntry: PlatformStatesCatalogEntry = { - // TODO: change with the PK type - PK: `ESERVICEDESCRIPTOR#${eserviceId}#${descriptor.id}`, + PK: eserviceDescriptorPK, state: descriptorStateToClientState(descriptor.state), descriptorAudience: descriptor.audience[0], version: msg.version, updatedAt: new Date().toISOString(), }; await writeCatalogEntry(catalogEntry, dynamoDBClient); + + // TO DO token-generation-states part + } else { + // activation from suspended + await updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, + existingCatalogEntry.PK, + descriptorStateToClientState(descriptor.state), + msg.version + ); + + // TO DO token-generation-states part } - ) + }) + .with(descriptorState.suspended, async () => { + const catalogEntry: PlatformStatesCatalogEntry = { + PK: makePlatformStatesEServiceDescriptorPK({ + eserviceId, + descriptorId: descriptor.id, + }), + state: descriptorStateToClientState(descriptor.state), + descriptorAudience: descriptor.audience[0], + version: msg.version, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(catalogEntry, dynamoDBClient); + + // TO DO token-generation-states part + }) .with(descriptorState.archived, async () => { const eserviceId = msg.data.eserviceId; const descriptorV1 = msg.data.eserviceDescriptor; diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index f49c55502e..2d5d284ae3 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -6,7 +6,6 @@ import { genericInternalError, itemState, ItemState, - itemState, PlatformStatesCatalogEntry, TokenGenerationStatesClientPurposeEntry, } from "pagopa-interop-models"; @@ -150,6 +149,43 @@ export const updateDescriptorState = async ( await dynamoDBClient.send(command); }; +export const updateDescriptorStateInPlatformStatesEntry = async ( + dynamoDBClient: DynamoDBClient, + primaryKey: string, + state: ItemState, + version: number +): Promise => { + const input: UpdateItemInput = { + Key: { + PK: { + S: primaryKey, + }, + }, + ExpressionAttributeValues: { + ":newState": { + S: state, + }, + ":newVersion": { + N: version.toString(), + }, + ":newUpdateAt": { + S: new Date().toISOString(), + }, + }, + ExpressionAttributeNames: { + "#descriptorState": "state", + }, + UpdateExpression: + "SET #descriptorState = :newState, version = :newVersion, updatedAt = :newUpdateAt", + TableName: config.tokenGenerationReadModelTableNamePlatform, + ReturnValues: "ALL_NEW", + }; + const command = new UpdateItemCommand(input); + console.log(command); + const a = await dynamoDBClient.send(command); + console.log(a); +}; + export const writeTokenStateEntry = async ( tokenStateEntry: TokenGenerationStatesClientPurposeEntry, dynamoDBClient: DynamoDBClient diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 220a74ffea..19b1122ed1 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -291,7 +291,7 @@ describe("database test", async () => { }; const primaryKey = makePlatformStatesEServiceDescriptorPK({ eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, + descriptorId: suspendedDescriptor.id, }); const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, From 879fcac3185f3fd8f975549f11eeb6988989a91f Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 30 Aug 2024 16:49:55 +0200 Subject: [PATCH 059/241] Update logic --- .../src/consumerServiceV1.ts | 27 ++++++++----------- .../catalog-platformstate-writer/src/utils.ts | 7 ++--- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index 0084991ffb..6b1891797a 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -35,7 +35,10 @@ export async function handleMessageV1( ); } const descriptor = fromDescriptorV1(descriptorV1); - + const eserviceDescriptorPK = makePlatformStatesEServiceDescriptorPK({ + eserviceId, + descriptorId: descriptor.id, + }); match(descriptor.state) .with(descriptorState.published, async () => { // steps: @@ -43,10 +46,7 @@ export async function handleMessageV1( // fare query su platform states e vedere se c'è // se non c'è sono in draft, e continuo questa esecuzione // se c'è (presumibilmente come inactive) allora era suspended e sono nel caso sotto (sospensione e riattivazione hanno stesso handler) - const eserviceDescriptorPK = makePlatformStatesEServiceDescriptorPK({ - eserviceId, - descriptorId: descriptor.id, - }); + const existingCatalogEntry = await readCatalogEntry( eserviceDescriptorPK, dynamoDBClient @@ -77,17 +77,12 @@ export async function handleMessageV1( } }) .with(descriptorState.suspended, async () => { - const catalogEntry: PlatformStatesCatalogEntry = { - PK: makePlatformStatesEServiceDescriptorPK({ - eserviceId, - descriptorId: descriptor.id, - }), - state: descriptorStateToClientState(descriptor.state), - descriptorAudience: descriptor.audience[0], - version: msg.version, - updatedAt: new Date().toISOString(), - }; - await writeCatalogEntry(catalogEntry, dynamoDBClient); + await updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, + eserviceDescriptorPK, + descriptorStateToClientState(descriptor.state), + msg.version + ); // TO DO token-generation-states part }) diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index 2d5d284ae3..5d226aa0c8 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -262,13 +262,10 @@ export const readTokenStateEntryByEServiceIdAndDescriptorId = async ( const input: QueryInput = { TableName: config.tokenGenerationReadModelTableNameTokenGeneration, IndexName: "gsiIndex", // Use the name of your Global Secondary Index - KeyConditionExpression: `GSIPK_eserviceId_descriptorId = :gsi_value`, + KeyConditionExpression: `GSIPK_eserviceId_descriptorId = :gsiValue`, ExpressionAttributeValues: { - ":gsi_value": { S: eserviceId_descriptorId }, + ":gsiValue": { S: eserviceId_descriptorId }, }, - // ExpressionAttributeNames: { - // "#gsi": "GSIPK_eserviceId_descriptorId", - // }, ScanIndexForward: false, }; const command = new QueryCommand(input); From cba78adc9231be82abda3940474961fbd4737eef Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 2 Sep 2024 14:19:13 +0200 Subject: [PATCH 060/241] Renaming --- packages/catalog-platformstate-writer/src/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index 5d226aa0c8..ff08dabffc 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -173,10 +173,10 @@ export const updateDescriptorStateInPlatformStatesEntry = async ( }, }, ExpressionAttributeNames: { - "#descriptorState": "state", + "#state": "state", }, UpdateExpression: - "SET #descriptorState = :newState, version = :newVersion, updatedAt = :newUpdateAt", + "SET #state = :newState, version = :newVersion, updatedAt = :newUpdateAt", TableName: config.tokenGenerationReadModelTableNamePlatform, ReturnValues: "ALL_NEW", }; From b00d48f59b467407f167e8297e5c095979b5f6bd Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 2 Sep 2024 14:34:49 +0200 Subject: [PATCH 061/241] Fix types usage --- .../src/consumerServiceV2.ts | 15 +++++++++++---- .../catalog-platformstate-writer/src/utils.ts | 5 +++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index a3e9c8ee6e..a2d60cd7de 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -6,6 +6,7 @@ import { fromEServiceV2, genericInternalError, itemState, + makeGSIPKEServiceIdDescriptorId, makePlatformStatesEServiceDescriptorPK, PlatformStatesCatalogEntry, unsafeBrandId, @@ -82,7 +83,7 @@ export async function handleMessageV2( } const eservice = fromEServiceV2(eserviceV2); - const descriptorId = msg.data.descriptorId; + const descriptorId = unsafeBrandId(msg.data.descriptorId); const primaryKey = makePlatformStatesEServiceDescriptorPK({ eserviceId: eservice.id, descriptorId: unsafeBrandId(descriptorId), @@ -111,7 +112,10 @@ export async function handleMessageV2( await writeCatalogEntry(updatedCatalogEntry, dynamoDBClient); // token-generation-states - const eserviceId_descriptorId = `${eservice.id}#${descriptorId}`; + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId, + }); const result = await readTokenStateEntriesByEserviceIdAndDescriptorId( eserviceId_descriptorId, dynamoDBClient @@ -149,8 +153,11 @@ export async function handleMessageV2( await deleteCatalogEntry(primaryKey, dynamoDBClient); // token-generation-states - const descriptorId = msg.data.descriptorId; - const eserviceId_descriptorId = `${eservice.id}#${descriptorId}`; + const descriptorId = unsafeBrandId(msg.data.descriptorId); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId, + }); const entriesToUpdate = await readTokenStateEntriesByEserviceIdAndDescriptorId( eserviceId_descriptorId, diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index bcdd87bb18..92b04a581c 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -4,6 +4,7 @@ import { descriptorState, DescriptorState, genericInternalError, + GSIPKEServiceIdDescriptorId, itemState, ItemState, PlatformStatesCatalogEntry, @@ -218,7 +219,7 @@ export const writeTokenStateEntry = async ( }; export const readTokenStateEntryByEServiceIdAndDescriptorId = async ( - eserviceId_descriptorId: string, + eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, dynamoDBClient: DynamoDBClient ): Promise => { console.log("eserviceId_descriptorId ", eserviceId_descriptorId); @@ -257,7 +258,7 @@ export const readTokenStateEntryByEServiceIdAndDescriptorId = async ( }; export const readTokenStateEntriesByEserviceIdAndDescriptorId = async ( - eserviceId_descriptorId: string, + eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, dynamoDBClient: DynamoDBClient ): Promise => { console.log("eserviceId_descriptorId ", eserviceId_descriptorId); From ddd873f5d4741f84f1289be482b9b3653c34ef38 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 2 Sep 2024 15:11:21 +0200 Subject: [PATCH 062/241] Add logic --- .../src/consumerServiceV1.ts | 43 +++++++++++++++++-- .../catalog-platformstate-writer/src/utils.ts | 26 +++++++++++ 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index 6b1891797a..c88bd7913b 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -5,6 +5,7 @@ import { EServiceId, fromDescriptorV1, genericInternalError, + makeGSIPKEServiceIdDescriptorId, makePlatformStatesEServiceDescriptorPK, PlatformStatesCatalogEntry, unsafeBrandId, @@ -15,6 +16,7 @@ import { descriptorStateToClientState, readCatalogEntry, updateDescriptorStateInPlatformStatesEntry, + updateEntriesInTokenGenerationStatesTable, writeCatalogEntry, } from "./utils.js"; @@ -63,9 +65,11 @@ export async function handleMessageV1( }; await writeCatalogEntry(catalogEntry, dynamoDBClient); - // TO DO token-generation-states part + // TO DO token-generation-states } else { // activation from suspended + + // platform-states await updateDescriptorStateInPlatformStatesEntry( dynamoDBClient, existingCatalogEntry.PK, @@ -73,10 +77,20 @@ export async function handleMessageV1( msg.version ); - // TO DO token-generation-states part + // token-generation-states + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId, + descriptorId: descriptor.id, + }); + await updateEntriesInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptor.state, + dynamoDBClient + ); } }) .with(descriptorState.suspended, async () => { + // platform-states await updateDescriptorStateInPlatformStatesEntry( dynamoDBClient, eserviceDescriptorPK, @@ -84,10 +98,19 @@ export async function handleMessageV1( msg.version ); - // TO DO token-generation-states part + // token-generation-states + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId, + descriptorId: descriptor.id, + }); + await updateEntriesInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptor.state, + dynamoDBClient + ); }) .with(descriptorState.archived, async () => { - const eserviceId = msg.data.eserviceId; + const eserviceId = unsafeBrandId(msg.data.eserviceId); const descriptorV1 = msg.data.eserviceDescriptor; if (!descriptorV1) { throw genericInternalError( @@ -96,8 +119,20 @@ export async function handleMessageV1( } const descriptor = fromDescriptorV1(descriptorV1); + // platform-states const primaryKey = `ESERVICEDESCRIPTOR#${eserviceId}#${descriptor.id}`; await deleteCatalogEntry(primaryKey, dynamoDBClient); + + // token-generation-states + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId, + descriptorId: descriptor.id, + }); + await updateEntriesInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptor.state, + dynamoDBClient + ); }) .with(descriptorState.draft, descriptorState.deprecated, () => Promise.resolve() diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index 7744daa34b..136f088ac3 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -339,3 +339,29 @@ export const sleep = (ms: number, mockDate = new Date()): Promise => vi.useFakeTimers(); vi.setSystemTime(mockDate); }); + +export const updateEntriesInTokenGenerationStatesTable = async ( + eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, + descriptorState: DescriptorState, + dynamoDBClient: DynamoDBClient +): Promise => { + const entriesToUpdate = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + + if (entriesToUpdate) { + for (const entry of entriesToUpdate) { + await updateDescriptorState( + dynamoDBClient, + entry.PK, + descriptorStateToClientState(descriptorState) + ); + } + } else { + throw genericInternalError( + `Unable to find token generation state entry with GSIPK_eserviceId_descriptorId ${eserviceId_descriptorId}` + ); + } +}; From f985c572406fffbbb74b6078fef6f896e52ee95f Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 2 Sep 2024 15:11:28 +0200 Subject: [PATCH 063/241] Improve tests --- ...logPlatformstateWriter.integration.test.ts | 172 +++++++++++++++++- 1 file changed, 171 insertions(+), 1 deletion(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 19b1122ed1..3602e33b0d 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -244,6 +244,38 @@ describe("database test", async () => { updatedAt: new Date().toISOString(), }; await writeCatalogEntry(previousStateEntry, dynamoDBClient); + + // token-generation-states + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + await handleMessageV1(message, dynamoDBClient); await sleep(1000, mockDate); @@ -254,6 +286,32 @@ describe("database test", async () => { version: 2, }; expect(retrievedEntry).toEqual(expectedEntry); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: itemState.active, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: itemState.active, + updatedAt: new Date().toISOString(), + }; + console.log(previousTokenStateEntry1, previousTokenStateEntry2); + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry1, + expectedTokenStateEntry2, + ]) + ); }); it("EServiceDescriptorUpdated (published -> suspended)", async () => { @@ -301,6 +359,37 @@ describe("database test", async () => { updatedAt: new Date().toISOString(), }; await writeCatalogEntry(previousStateEntry, dynamoDBClient); + + // token-generation-states + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); await handleMessageV1(message, dynamoDBClient); await sleep(1000, mockDate); @@ -311,9 +400,33 @@ describe("database test", async () => { version: 2, }; expect(retrievedEntry).toEqual(expectedEntry); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry1, + expectedTokenStateEntry2, + ]) + ); }); - // descriptorState.published for archiving it("EServiceDescriptorUpdated (published -> archived)", async () => { const publishedDescriptor: Descriptor = { ...getMockDescriptor(), @@ -359,11 +472,68 @@ describe("database test", async () => { updatedAt: new Date().toISOString(), }; await writeCatalogEntry(previousStateEntry, dynamoDBClient); + + // token-generation-states + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + await handleMessageV1(message, dynamoDBClient); await sleep(1000, mockDate); const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); expect(retrievedEntry).toBeUndefined(); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry1, + expectedTokenStateEntry2, + ]) + ); }); }); From 591f3e0d14a8487bd4d08d71ed7831a4251220cc Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 2 Sep 2024 15:12:29 +0200 Subject: [PATCH 064/241] Add util function --- .../catalog-platformstate-writer/src/utils.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index 92b04a581c..92749c70be 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -305,3 +305,29 @@ export const sleep = (ms: number, mockDate = new Date()): Promise => vi.useFakeTimers(); vi.setSystemTime(mockDate); }); + +export const updateEntriesInTokenGenerationStatesTable = async ( + eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, + descriptorState: DescriptorState, + dynamoDBClient: DynamoDBClient +): Promise => { + const entriesToUpdate = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + + if (entriesToUpdate) { + for (const entry of entriesToUpdate) { + await updateDescriptorState( + dynamoDBClient, + entry.PK, + descriptorStateToClientState(descriptorState) + ); + } + } else { + throw genericInternalError( + `Unable to find token generation state entry with GSIPK_eserviceId_descriptorId ${eserviceId_descriptorId}` + ); + } +}; From 655e7ac57f029da45474e3aa61300d7696226910 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 2 Sep 2024 15:18:23 +0200 Subject: [PATCH 065/241] Refactor --- .../src/consumerServiceV2.ts | 58 ++++++------------- 1 file changed, 17 insertions(+), 41 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index a2d60cd7de..85d3a1b918 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -5,7 +5,6 @@ import { EServiceEventEnvelopeV2, fromEServiceV2, genericInternalError, - itemState, makeGSIPKEServiceIdDescriptorId, makePlatformStatesEServiceDescriptorPK, PlatformStatesCatalogEntry, @@ -16,8 +15,7 @@ import { deleteCatalogEntry, descriptorStateToClientState, readCatalogEntry, - readTokenStateEntriesByEserviceIdAndDescriptorId, - updateDescriptorState, + updateEntriesInTokenGenerationStatesTable, writeCatalogEntry, } from "./utils.js"; @@ -84,6 +82,14 @@ export async function handleMessageV2( const eservice = fromEServiceV2(eserviceV2); const descriptorId = unsafeBrandId(msg.data.descriptorId); + const descriptor = eservice.descriptors.find( + (d) => d.id === descriptorId + ); + if (!descriptor) { + throw genericInternalError( + `Unable to find descriptor with id ${descriptorId}` + ); + } const primaryKey = makePlatformStatesEServiceDescriptorPK({ eserviceId: eservice.id, descriptorId: unsafeBrandId(descriptorId), @@ -102,10 +108,7 @@ export async function handleMessageV2( const updatedCatalogEntry: PlatformStatesCatalogEntry = { ...catalogEntry, - state: - msg.type === "EServiceDescriptorActivated" - ? itemState.active - : itemState.inactive, + state: descriptorStateToClientState(descriptor.state), version: msg.version, updatedAt: new Date().toISOString(), }; @@ -116,24 +119,11 @@ export async function handleMessageV2( eserviceId: eservice.id, descriptorId, }); - const result = await readTokenStateEntriesByEserviceIdAndDescriptorId( + await updateEntriesInTokenGenerationStatesTable( eserviceId_descriptorId, + descriptor.state, dynamoDBClient ); - - if (result) { - for (const entry of result) { - await updateDescriptorState( - dynamoDBClient, - entry.PK, - updatedCatalogEntry.state - ); - } - } else { - throw genericInternalError( - `Unable to find token generation state entries with GSIPK_eserviceId_descriptorId ${eserviceId_descriptorId}` - ); - } } } ) @@ -158,25 +148,11 @@ export async function handleMessageV2( eserviceId: eservice.id, descriptorId, }); - const entriesToUpdate = - await readTokenStateEntriesByEserviceIdAndDescriptorId( - eserviceId_descriptorId, - dynamoDBClient - ); - - if (entriesToUpdate) { - for (const entry of entriesToUpdate) { - await updateDescriptorState( - dynamoDBClient, - entry.PK, - descriptorStateToClientState(descriptorState.archived) - ); - } - } else { - throw genericInternalError( - `Unable to find token generation state entry with GSIPK_eserviceId_descriptorId ${eserviceId_descriptorId}` - ); - } + await updateEntriesInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptorState.archived, + dynamoDBClient + ); }) .with( { type: "EServiceDeleted" }, From e4a0e39a954bdd9bdde38effe812b531c6625929 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 2 Sep 2024 16:50:33 +0200 Subject: [PATCH 066/241] Update utils --- .../catalog-platformstate-writer/src/utils.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index 92749c70be..c93427fe11 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -32,6 +32,7 @@ import { unmarshall } from "@aws-sdk/util-dynamodb"; import { z } from "zod"; import { config } from "./config/config.js"; +// TO DO scrivere test per vedere se funziona come upsert o solo come update export const writeCatalogEntry = async ( catalogEntry: PlatformStatesCatalogEntry, dynamoDBClient: DynamoDBClient @@ -122,6 +123,7 @@ export const descriptorStateToClientState = ( ? itemState.active : itemState.inactive; +// TO DO scrivere test per vedere se funziona come upsert o solo come update export const updateDescriptorState = async ( dynamoDBClient: DynamoDBClient, primaryKey: string, @@ -150,6 +152,43 @@ export const updateDescriptorState = async ( await dynamoDBClient.send(command); }; +export const updateDescriptorStateInPlatformStatesEntry = async ( + dynamoDBClient: DynamoDBClient, + primaryKey: string, + state: ItemState, + version: number +): Promise => { + const input: UpdateItemInput = { + Key: { + PK: { + S: primaryKey, + }, + }, + ExpressionAttributeValues: { + ":newState": { + S: state, + }, + ":newVersion": { + N: version.toString(), + }, + ":newUpdateAt": { + S: new Date().toISOString(), + }, + }, + ExpressionAttributeNames: { + "#state": "state", + }, + UpdateExpression: + "SET #state = :newState, version = :newVersion, updatedAt = :newUpdateAt", + TableName: config.tokenGenerationReadModelTableNamePlatform, + ReturnValues: "ALL_NEW", + }; + const command = new UpdateItemCommand(input); + console.log(command); + const a = await dynamoDBClient.send(command); + console.log(a); +}; + export const writeTokenStateEntry = async ( tokenStateEntry: TokenGenerationStatesClientPurposeEntry, dynamoDBClient: DynamoDBClient From afcdc4534b87c9a8b830795e1f5e685f77f1101b Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 2 Sep 2024 17:04:11 +0200 Subject: [PATCH 067/241] Fix logic --- .../src/consumerServiceV2.ts | 171 ++++++++++++------ .../catalog-platformstate-writer/src/utils.ts | 2 +- 2 files changed, 115 insertions(+), 58 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index 85d3a1b918..01f845977c 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -1,8 +1,11 @@ import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; import { + Descriptor, DescriptorId, descriptorState, + EService, EServiceEventEnvelopeV2, + EServiceV2, fromEServiceV2, genericInternalError, makeGSIPKEServiceIdDescriptorId, @@ -15,7 +18,8 @@ import { deleteCatalogEntry, descriptorStateToClientState, readCatalogEntry, - updateEntriesInTokenGenerationStatesTable, + updateDescriptorStateInPlatformStatesEntry, + updateDescriptorStateInTokenGenerationStatesTable, writeCatalogEntry, } from "./utils.js"; @@ -25,74 +29,109 @@ export async function handleMessageV2( ): Promise { await match(message) .with({ type: "EServiceDescriptorPublished" }, async (msg) => { - const descriptorId = msg.data.descriptorId; - const eserviceV2 = msg.data.eservice; - if (!eserviceV2) { - throw genericInternalError( - `EService not found in message data for event ${msg.type}` + const { eservice, descriptor } = parseEServiceAndDescriptor( + msg.data.eservice, + unsafeBrandId(msg.data.descriptorId) + ); + const previousDescriptor = eservice.descriptors.find( + (d) => d.version === (Number(descriptor.version) - 1).toString() + ); + + // flow for current descriptor + { + const primaryKeyCurrent = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: descriptor.id, + }); + const existingCatalogEntryCurrent = await readCatalogEntry( + primaryKeyCurrent, + dynamoDBClient ); - } + if ( + existingCatalogEntryCurrent && + existingCatalogEntryCurrent.version > msg.version + ) { + // Stops processing if the message is older than the catalog entry + return; + } - const eservice = fromEServiceV2(eserviceV2); + const catalogEntry: PlatformStatesCatalogEntry = { + PK: primaryKeyCurrent, + state: descriptorStateToClientState(descriptor.state), + descriptorAudience: descriptor.audience[0], + version: msg.version, + updatedAt: new Date().toISOString(), + }; - const descriptor = eservice.descriptors.find( - (d) => d.id === descriptorId - ); - if (!descriptor) { - throw genericInternalError( - `Unable to find descriptor with id ${descriptorId}` + await writeCatalogEntry(catalogEntry, dynamoDBClient); + + // token-generation-states + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: descriptor.id, + }); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptor.state, + dynamoDBClient ); } - const primaryKey = makePlatformStatesEServiceDescriptorPK({ + + // flow for previous descriptor + + if (!previousDescriptor) { + return Promise.resolve(); + } + const primaryKeyPrevious = makePlatformStatesEServiceDescriptorPK({ eserviceId: eservice.id, descriptorId: descriptor.id, }); - const existingCatalogEntry = await readCatalogEntry( - primaryKey, + const existingCatalogEntryPrevious = await readCatalogEntry( + primaryKeyPrevious, dynamoDBClient ); - - // Stops processing if the message is older than the catalog entry - if (existingCatalogEntry && existingCatalogEntry.version > msg.version) { - return; + if ( + existingCatalogEntryPrevious && + existingCatalogEntryPrevious.version > msg.version + ) { + return Promise.resolve(); } - const catalogEntry: PlatformStatesCatalogEntry = { - PK: primaryKey, - state: descriptorStateToClientState(descriptor.state), - descriptorAudience: descriptor.audience[0], - version: msg.version, - updatedAt: new Date().toISOString(), - }; + if (!existingCatalogEntryPrevious) { + throw genericInternalError( + `Unable to find catalog entry with PK ${primaryKeyPrevious}` + ); + } - await writeCatalogEntry(catalogEntry, dynamoDBClient); + await updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, + primaryKeyPrevious, + descriptorStateToClientState(previousDescriptor.state), + msg.version + ); - // TODO: Add token-generation-states part + // token-generation-states + const eserviceId_descriptorId_previous = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: previousDescriptor.id, + }); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId_previous, + descriptor.state, + dynamoDBClient + ); }) .with( { type: "EServiceDescriptorActivated" }, { type: "EServiceDescriptorSuspended" }, async (msg) => { - const eserviceV2 = msg.data.eservice; - if (!eserviceV2) { - throw genericInternalError( - `EService not found in message data for event ${msg.type}` - ); - } - - const eservice = fromEServiceV2(eserviceV2); - const descriptorId = unsafeBrandId(msg.data.descriptorId); - const descriptor = eservice.descriptors.find( - (d) => d.id === descriptorId + const { eservice, descriptor } = parseEServiceAndDescriptor( + msg.data.eservice, + unsafeBrandId(msg.data.descriptorId) ); - if (!descriptor) { - throw genericInternalError( - `Unable to find descriptor with id ${descriptorId}` - ); - } const primaryKey = makePlatformStatesEServiceDescriptorPK({ eserviceId: eservice.id, - descriptorId: unsafeBrandId(descriptorId), + descriptorId: descriptor.id, }); const catalogEntry = await readCatalogEntry(primaryKey, dynamoDBClient); @@ -106,20 +145,19 @@ export async function handleMessageV2( return; } - const updatedCatalogEntry: PlatformStatesCatalogEntry = { - ...catalogEntry, - state: descriptorStateToClientState(descriptor.state), - version: msg.version, - updatedAt: new Date().toISOString(), - }; - await writeCatalogEntry(updatedCatalogEntry, dynamoDBClient); + await updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, + primaryKey, + descriptorStateToClientState(descriptor.state), + msg.version + ); // token-generation-states const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, - descriptorId, + descriptorId: descriptor.id, }); - await updateEntriesInTokenGenerationStatesTable( + await updateDescriptorStateInTokenGenerationStatesTable( eserviceId_descriptorId, descriptor.state, dynamoDBClient @@ -148,7 +186,7 @@ export async function handleMessageV2( eserviceId: eservice.id, descriptorId, }); - await updateEntriesInTokenGenerationStatesTable( + await updateDescriptorStateInTokenGenerationStatesTable( eserviceId_descriptorId, descriptorState.archived, dynamoDBClient @@ -177,3 +215,22 @@ export async function handleMessageV2( ) .exhaustive(); } + +export const parseEServiceAndDescriptor = ( + eserviceV2: EServiceV2 | undefined, + descriptorId: DescriptorId +): { eservice: EService; descriptor: Descriptor } => { + if (!eserviceV2) { + throw genericInternalError(`EService not found in message data`); + } + + const eservice = fromEServiceV2(eserviceV2); + + const descriptor = eservice.descriptors.find((d) => d.id === descriptorId); + if (!descriptor) { + throw genericInternalError( + `Unable to find descriptor with id ${descriptorId}` + ); + } + return { eservice, descriptor }; +}; diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index c93427fe11..f173c9971b 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -345,7 +345,7 @@ export const sleep = (ms: number, mockDate = new Date()): Promise => vi.setSystemTime(mockDate); }); -export const updateEntriesInTokenGenerationStatesTable = async ( +export const updateDescriptorStateInTokenGenerationStatesTable = async ( eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, descriptorState: DescriptorState, dynamoDBClient: DynamoDBClient From 1ae0c4092922fc62649e8a3fd9939a5cffd7b680 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 2 Sep 2024 17:15:39 +0200 Subject: [PATCH 068/241] Improve test --- ...logPlatformstateWriter.integration.test.ts | 213 ++++++++++-------- 1 file changed, 115 insertions(+), 98 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 51c9a187cd..32c8a206bf 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -384,111 +384,128 @@ describe("database test", async () => { ); }); - it("EServiceDescriptorPublished", async () => { - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - audience: ["pagopa.it"], - interface: getMockDocument(), - state: descriptorState.draft, - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const publishedDescriptor: Descriptor = { - ...draftDescriptor, - publishedAt: new Date(), - state: descriptorState.published, - }; - const updatedEService: EService = { - ...eservice, - descriptors: [publishedDescriptor], - }; - const payload: EServiceDescriptorPublishedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: publishedDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorPublished", - event_version: 2, - data: payload, - log_date: new Date(), - }; - await handleMessageV2(message, dynamoDBClient); + describe("EServiceDescriptorPublished (the eservice has 1 descriptor)", () => { + it("EServiceDescriptorPublished", async () => { + const draftDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.draft, + }; + const eservice: EService = { + ...mockEService, + descriptors: [draftDescriptor], + }; + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: updatedEService.id, - descriptorId: publishedDescriptor.id, + const publishedDescriptor: Descriptor = { + ...draftDescriptor, + publishedAt: new Date(), + state: descriptorState.published, + }; + const updatedEService: EService = { + ...eservice, + descriptors: [publishedDescriptor], + }; + const payload: EServiceDescriptorPublishedV2 = { + eservice: toEServiceV2(updatedEService), + descriptorId: publishedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDescriptorPublished", + event_version: 2, + data: payload, + log_date: new Date(), + }; + await handleMessageV2(message, dynamoDBClient); + + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: updatedEService.id, + descriptorId: publishedDescriptor.id, + }); + const retrievedEntry = await readCatalogEntry( + primaryKey, + dynamoDBClient + ); + const expectedEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + version: 2, + updatedAt: new Date().toISOString(), + }; + expect(retrievedEntry).toEqual(expectedEntry); }); - const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); - const expectedEntry: PlatformStatesCatalogEntry = { - PK: primaryKey, - state: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], - version: 2, - updatedAt: new Date().toISOString(), - }; - expect(retrievedEntry).toEqual(expectedEntry); - }); - // TODO: add test with incoming version 1 and previous entry version 1? - it("EServiceDescriptorPublished - no operation if entry already exists. Incoming has version 1; previous entry has version 2", async () => { - const draftDescriptor: Descriptor = { - ...getMockDescriptor(), - audience: ["pagopa.it"], - interface: getMockDocument(), - state: descriptorState.draft, - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // TODO: add test with incoming version 1 and previous entry version 1? + it("EServiceDescriptorPublished - no operation if the entry already exists. Incoming has version 1; previous entry has version 2", async () => { + const draftDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.draft, + }; + const eservice: EService = { + ...mockEService, + descriptors: [draftDescriptor], + }; + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - const publishedDescriptor: Descriptor = { - ...draftDescriptor, - publishedAt: new Date(), - state: descriptorState.published, - }; - const updatedEService: EService = { - ...eservice, - descriptors: [publishedDescriptor], - }; - const payload: EServiceDescriptorPublishedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: publishedDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 1, - type: "EServiceDescriptorPublished", - event_version: 2, - data: payload, - log_date: new Date(), - }; + const publishedDescriptor: Descriptor = { + ...draftDescriptor, + publishedAt: new Date(), + state: descriptorState.published, + }; + const updatedEService: EService = { + ...eservice, + descriptors: [publishedDescriptor], + }; + const payload: EServiceDescriptorPublishedV2 = { + eservice: toEServiceV2(updatedEService), + descriptorId: publishedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 1, + type: "EServiceDescriptorPublished", + event_version: 2, + data: payload, + log_date: new Date(), + }; - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: updatedEService.id, - descriptorId: publishedDescriptor.id, + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: updatedEService.id, + descriptorId: publishedDescriptor.id, + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + version: 2, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); + await handleMessageV2(message, dynamoDBClient); + + const retrievedEntry = await readCatalogEntry( + primaryKey, + dynamoDBClient + ); + expect(retrievedEntry).toEqual(previousStateEntry); }); - const previousStateEntry: PlatformStatesCatalogEntry = { - PK: primaryKey, - state: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], - version: 2, - updatedAt: new Date().toISOString(), - }; - await writeCatalogEntry(previousStateEntry, dynamoDBClient); - await handleMessageV2(message, dynamoDBClient); + }); - const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); - expect(retrievedEntry).toEqual(previousStateEntry); + describe("EServiceDescriptorPublished (the previous descriptor becomes archived)", () => { + it("no operation if the entry already exists: incoming has version 1; previous entry has version 2", () => { + expect(1).toBe(1); + }); + it("entry has to be updated: incoming has version 3; previous entry has version 2", () => { + expect(1).toBe(1); + }); }); it("EServiceDescriptorSuspended", async () => { From 16ed1e0a78661eb806ab78e373fa1a8565a23d46 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 2 Sep 2024 17:17:03 +0200 Subject: [PATCH 069/241] Fix --- .../catalog-platformstate-writer/src/consumerServiceV1.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index c88bd7913b..0748a59b20 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -16,7 +16,7 @@ import { descriptorStateToClientState, readCatalogEntry, updateDescriptorStateInPlatformStatesEntry, - updateEntriesInTokenGenerationStatesTable, + updateDescriptorStateInTokenGenerationStatesTable, writeCatalogEntry, } from "./utils.js"; @@ -82,7 +82,7 @@ export async function handleMessageV1( eserviceId, descriptorId: descriptor.id, }); - await updateEntriesInTokenGenerationStatesTable( + await updateDescriptorStateInTokenGenerationStatesTable( eserviceId_descriptorId, descriptor.state, dynamoDBClient @@ -103,7 +103,7 @@ export async function handleMessageV1( eserviceId, descriptorId: descriptor.id, }); - await updateEntriesInTokenGenerationStatesTable( + await updateDescriptorStateInTokenGenerationStatesTable( eserviceId_descriptorId, descriptor.state, dynamoDBClient @@ -128,7 +128,7 @@ export async function handleMessageV1( eserviceId, descriptorId: descriptor.id, }); - await updateEntriesInTokenGenerationStatesTable( + await updateDescriptorStateInTokenGenerationStatesTable( eserviceId_descriptorId, descriptor.state, dynamoDBClient From e43d3bb72ba9dd47427ed044b53cc12497c66d9f Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 2 Sep 2024 17:23:43 +0200 Subject: [PATCH 070/241] Add logic --- .../src/consumerServiceV1.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index 0748a59b20..7251fd311b 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -65,7 +65,16 @@ export async function handleMessageV1( }; await writeCatalogEntry(catalogEntry, dynamoDBClient); - // TO DO token-generation-states + // token-generation-states + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId, + descriptorId: descriptor.id, + }); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptor.state, + dynamoDBClient + ); } else { // activation from suspended From 48ce748803dc334c2381a4228113af9eeaf124b7 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 2 Sep 2024 17:26:52 +0200 Subject: [PATCH 071/241] Add comment --- packages/catalog-platformstate-writer/src/consumerServiceV2.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index 01f845977c..223658e5d8 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -55,6 +55,8 @@ export async function handleMessageV2( return; } + // no previous entry or entry has to be updated + const catalogEntry: PlatformStatesCatalogEntry = { PK: primaryKeyCurrent, state: descriptorStateToClientState(descriptor.state), From 9decc202fe542f7fe9356b8ce173bc895b5842d2 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 2 Sep 2024 17:27:07 +0200 Subject: [PATCH 072/241] Improve test cases --- ...logPlatformstateWriter.integration.test.ts | 453 +++++++++--------- 1 file changed, 233 insertions(+), 220 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 32c8a206bf..5f96cc9de6 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -145,132 +145,137 @@ describe("database test", async () => { describe("Events V2", async () => { const mockEService = getMockEService(); - it("EServiceDescriptorActivated", async () => { - const suspendedDescriptor: Descriptor = { - ...getMockDescriptor(), - audience: ["pagopa.it"], - interface: getMockDocument(), - state: descriptorState.suspended, - publishedAt: new Date(), - suspendedAt: new Date(), - }; - const eservice: EService = { - ...mockEService, - descriptors: [suspendedDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const publishedDescriptor: Descriptor = { - ...suspendedDescriptor, - publishedAt: new Date(), - suspendedAt: new Date(), - state: descriptorState.published, - }; - const updatedEService: EService = { - ...eservice, - descriptors: [publishedDescriptor], - }; - const payload: EServiceDescriptorActivatedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: publishedDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorActivated", - event_version: 2, - data: payload, - log_date: new Date(), - }; - const catalogEntryPrimaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, - }); - const previousStateEntry: PlatformStatesCatalogEntry = { - PK: catalogEntryPrimaryKey, - state: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - version: 1, - updatedAt: new Date().toISOString(), - }; - await writeCatalogEntry(previousStateEntry, dynamoDBClient); - - // token-generation-states - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), - kid: generateId(), - }); - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, + describe("EServiceDescriptorActivated", () => { + it("no operation if the entry already exists: incoming has version 1; previous entry has version 2", () => { + expect(1).toBe(1); }); - const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), - descriptorState: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + it("entry has to be updated: incoming has version 3; previous entry has version 2", async () => { + const suspendedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.suspended, + publishedAt: new Date(), + suspendedAt: new Date(), }; - await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + const eservice: EService = { + ...mockEService, + descriptors: [suspendedDescriptor], + }; + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), - kid: generateId(), - }); - const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), - descriptorState: itemState.inactive, + const publishedDescriptor: Descriptor = { + ...suspendedDescriptor, + publishedAt: new Date(), + suspendedAt: new Date(), + state: descriptorState.published, + }; + const updatedEService: EService = { + ...eservice, + descriptors: [publishedDescriptor], + }; + const payload: EServiceDescriptorActivatedV2 = { + eservice: toEServiceV2(updatedEService), + descriptorId: publishedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDescriptorActivated", + event_version: 2, + data: payload, + log_date: new Date(), + }; + const catalogEntryPrimaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: catalogEntryPrimaryKey, + state: itemState.inactive, descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + version: 1, + updatedAt: new Date().toISOString(), }; - await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); - - await sleep(1000, mockDate); - await handleMessageV2(message, dynamoDBClient); + await writeCatalogEntry(previousStateEntry, dynamoDBClient); - // platform-states - const retrievedCatalogEntry = await readCatalogEntry( - catalogEntryPrimaryKey, - dynamoDBClient - ); - const expectedCatalogEntry: PlatformStatesCatalogEntry = { - ...previousStateEntry, - state: itemState.active, - version: 2, - updatedAt: new Date().toISOString(), - }; - expect(retrievedCatalogEntry).toEqual(expectedCatalogEntry); + // token-generation-states + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + + await sleep(1000, mockDate); + await handleMessageV2(message, dynamoDBClient); - // token-generation-states - const retrievedTokenStateEntries = - await readTokenStateEntriesByEserviceIdAndDescriptorId( - eserviceId_descriptorId, + // platform-states + const retrievedCatalogEntry = await readCatalogEntry( + catalogEntryPrimaryKey, dynamoDBClient ); - const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...previousTokenStateEntry1, - descriptorState: itemState.active, - updatedAt: new Date().toISOString(), - }; - const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...previousTokenStateEntry2, - descriptorState: itemState.active, + const expectedCatalogEntry: PlatformStatesCatalogEntry = { + ...previousStateEntry, + state: itemState.active, + version: 2, updatedAt: new Date().toISOString(), }; - - // TODO: this works, but arrayContaining must have the exact objects - // expect.arrayContaining([expectedTokenStateEntry2, expectedTokenStateEntry2]) also passes the test - expect(retrievedTokenStateEntries).toHaveLength(2); - expect(retrievedTokenStateEntries).toEqual( - expect.arrayContaining([ - expectedTokenStateEntry2, - expectedTokenStateEntry1, - ]) - ); + expect(retrievedCatalogEntry).toEqual(expectedCatalogEntry); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: itemState.active, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: itemState.active, + updatedAt: new Date().toISOString(), + }; + + // TODO: this works, but arrayContaining must have the exact objects + // expect.arrayContaining([expectedTokenStateEntry2, expectedTokenStateEntry2]) also passes the test + expect(retrievedTokenStateEntries).toHaveLength(2); + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry2, + expectedTokenStateEntry1, + ]) + ); + }); }); it("EServiceDescriptorArchived", async () => { @@ -508,124 +513,132 @@ describe("database test", async () => { }); }); - it("EServiceDescriptorSuspended", async () => { - const publishedDescriptor: Descriptor = { - ...getMockDescriptor(), - audience: ["pagopa.it"], - interface: getMockDocument(), - state: descriptorState.published, - publishedAt: new Date(), - }; - const eservice: EService = { - ...mockEService, - descriptors: [publishedDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const suspendedDescriptor: Descriptor = { - ...publishedDescriptor, - suspendedAt: new Date(), - state: descriptorState.suspended, - }; - const updatedEService: EService = { - ...eservice, - descriptors: [suspendedDescriptor], - }; - const payload: EServiceDescriptorSuspendedV2 = { - eservice: toEServiceV2(updatedEService), - descriptorId: suspendedDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: mockEService.id, - version: 2, - type: "EServiceDescriptorSuspended", - event_version: 2, - data: payload, - log_date: new Date(), - }; - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: updatedEService.id, - descriptorId: publishedDescriptor.id, - }); - const previousStateEntry: PlatformStatesCatalogEntry = { - PK: primaryKey, - state: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], - version: 1, - updatedAt: new Date().toISOString(), - }; - await writeCatalogEntry(previousStateEntry, dynamoDBClient); - - // token-generation-states - // TODO: replace last generateId() with kid - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), - kid: generateId(), - }); - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, + describe("EServiceDescriptorSuspended", () => { + it("no operation if the entry already exists: incoming has version 1; previous entry has version 2", () => { + expect(1).toBe(1); }); - const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), - descriptorState: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + it("entry has to be updated: incoming has version 3; previous entry has version 2", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.published, + publishedAt: new Date(), }; - await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + const eservice: EService = { + ...mockEService, + descriptors: [publishedDescriptor], + }; + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), - kid: generateId(), - }); - const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), - descriptorState: itemState.active, + const suspendedDescriptor: Descriptor = { + ...publishedDescriptor, + suspendedAt: new Date(), + state: descriptorState.suspended, + }; + const updatedEService: EService = { + ...eservice, + descriptors: [suspendedDescriptor], + }; + const payload: EServiceDescriptorSuspendedV2 = { + eservice: toEServiceV2(updatedEService), + descriptorId: suspendedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDescriptorSuspended", + event_version: 2, + data: payload, + log_date: new Date(), + }; + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: updatedEService.id, + descriptorId: publishedDescriptor.id, + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.active, descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + version: 1, + updatedAt: new Date().toISOString(), }; - await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + await writeCatalogEntry(previousStateEntry, dynamoDBClient); - await handleMessageV2(message, dynamoDBClient); + // token-generation-states + // TODO: replace last generateId() with kid + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); - const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); - const expectedEntry: PlatformStatesCatalogEntry = { - ...previousStateEntry, - state: itemState.inactive, - version: 2, - updatedAt: new Date().toISOString(), - }; - expect(retrievedEntry).toEqual(expectedEntry); + await handleMessageV2(message, dynamoDBClient); - // token-generation-states - const retrievedTokenStateEntries = - await readTokenStateEntriesByEserviceIdAndDescriptorId( - eserviceId_descriptorId, + const retrievedEntry = await readCatalogEntry( + primaryKey, dynamoDBClient ); - const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...previousTokenStateEntry1, - descriptorState: itemState.inactive, - updatedAt: new Date().toISOString(), - }; - const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...previousTokenStateEntry2, - descriptorState: itemState.inactive, + const expectedEntry: PlatformStatesCatalogEntry = { + ...previousStateEntry, + state: itemState.inactive, + version: 2, updatedAt: new Date().toISOString(), }; + expect(retrievedEntry).toEqual(expectedEntry); - expect(retrievedTokenStateEntries).toHaveLength(2); - expect(retrievedTokenStateEntries).toEqual( - expect.arrayContaining([ - expectedTokenStateEntry2, - expectedTokenStateEntry1, - ]) - ); + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + + expect(retrievedTokenStateEntries).toHaveLength(2); + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry2, + expectedTokenStateEntry1, + ]) + ); + }); }); }); }); From 74aad058da82e38faefbf6ab11ad50e4939b23b8 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 2 Sep 2024 17:50:25 +0200 Subject: [PATCH 073/241] Update logic --- .../src/consumerServiceV2.ts | 90 ++++++++++++++----- 1 file changed, 68 insertions(+), 22 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index 223658e5d8..899ecbf853 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -53,6 +53,48 @@ export async function handleMessageV2( ) { // Stops processing if the message is older than the catalog entry return; + } else if ( + existingCatalogEntryCurrent && + existingCatalogEntryCurrent.version <= msg.version + ) { + await updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, + primaryKeyCurrent, + descriptorStateToClientState(descriptor.state), + msg.version + ); + + // token-generation-states + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: descriptor.id, + }); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptor.state, + dynamoDBClient + ); + } else { + const catalogEntry: PlatformStatesCatalogEntry = { + PK: primaryKeyCurrent, + state: descriptorStateToClientState(descriptor.state), + descriptorAudience: descriptor.audience[0], + version: msg.version, + updatedAt: new Date().toISOString(), + }; + + await writeCatalogEntry(catalogEntry, dynamoDBClient); + + // token-generation-states + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: descriptor.id, + }); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptor.state, + dynamoDBClient + ); } // no previous entry or entry has to be updated @@ -97,31 +139,35 @@ export async function handleMessageV2( existingCatalogEntryPrevious.version > msg.version ) { return Promise.resolve(); - } + } else if ( + existingCatalogEntryPrevious && + existingCatalogEntryPrevious.version <= msg.version + ) { + await updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, + primaryKeyPrevious, + descriptorStateToClientState(previousDescriptor.state), + msg.version + ); - if (!existingCatalogEntryPrevious) { - throw genericInternalError( - `Unable to find catalog entry with PK ${primaryKeyPrevious}` + // token-generation-states + const eserviceId_descriptorId_previous = + makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: previousDescriptor.id, + }); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId_previous, + descriptor.state, + dynamoDBClient ); + } else { + if (!existingCatalogEntryPrevious) { + throw genericInternalError( + `Unable to find catalog entry with PK ${primaryKeyPrevious}` + ); + } } - - await updateDescriptorStateInPlatformStatesEntry( - dynamoDBClient, - primaryKeyPrevious, - descriptorStateToClientState(previousDescriptor.state), - msg.version - ); - - // token-generation-states - const eserviceId_descriptorId_previous = makeGSIPKEServiceIdDescriptorId({ - eserviceId: eservice.id, - descriptorId: previousDescriptor.id, - }); - await updateDescriptorStateInTokenGenerationStatesTable( - eserviceId_descriptorId_previous, - descriptor.state, - dynamoDBClient - ); }) .with( { type: "EServiceDescriptorActivated" }, From e64361846c432eb31b8d70c89f76a85ea1146c4a Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 2 Sep 2024 17:50:33 +0200 Subject: [PATCH 074/241] Improve test cases --- .../test/catalogPlatformstateWriter.integration.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 5f96cc9de6..7354976224 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -390,7 +390,7 @@ describe("database test", async () => { }); describe("EServiceDescriptorPublished (the eservice has 1 descriptor)", () => { - it("EServiceDescriptorPublished", async () => { + it("no previous entry", async () => { const draftDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], @@ -446,7 +446,7 @@ describe("database test", async () => { }); // TODO: add test with incoming version 1 and previous entry version 1? - it("EServiceDescriptorPublished - no operation if the entry already exists. Incoming has version 1; previous entry has version 2", async () => { + it("no operation if the entry already exists. Incoming has version 1; previous entry has version 2", async () => { const draftDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], @@ -502,6 +502,9 @@ describe("database test", async () => { ); expect(retrievedEntry).toEqual(previousStateEntry); }); + it("entry has to be updated: incoming has version 3; previous entry has version 2", async () => { + expect(1).toBe(1); + }); }); describe("EServiceDescriptorPublished (the previous descriptor becomes archived)", () => { From 4c0c11be9edffcea903dd7dcdc8c4b6062e25720 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 2 Sep 2024 17:57:13 +0200 Subject: [PATCH 075/241] Improve test cases --- .../test/catalogPlatformstateWriter.integration.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 7354976224..16a9eddfa2 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -508,12 +508,17 @@ describe("database test", async () => { }); describe("EServiceDescriptorPublished (the previous descriptor becomes archived)", () => { + // these tests start with the basic flow for the current descriptor (simple write operation). Then, additinal checks are added it("no operation if the entry already exists: incoming has version 1; previous entry has version 2", () => { expect(1).toBe(1); }); it("entry has to be updated: incoming has version 3; previous entry has version 2", () => { expect(1).toBe(1); }); + it("no previous entry", () => { + // to do throw error + expect(1).toBe(1); + }); }); describe("EServiceDescriptorSuspended", () => { From 3e394e087dc06fc6dbd61b88b0bfa2bc778de053 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 3 Sep 2024 11:10:06 +0200 Subject: [PATCH 076/241] Fix logic --- .../src/consumerServiceV2.ts | 61 +++---------------- 1 file changed, 9 insertions(+), 52 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index 899ecbf853..be93ec5e30 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -96,59 +96,22 @@ export async function handleMessageV2( dynamoDBClient ); } - - // no previous entry or entry has to be updated - - const catalogEntry: PlatformStatesCatalogEntry = { - PK: primaryKeyCurrent, - state: descriptorStateToClientState(descriptor.state), - descriptorAudience: descriptor.audience[0], - version: msg.version, - updatedAt: new Date().toISOString(), - }; - - await writeCatalogEntry(catalogEntry, dynamoDBClient); - - // token-generation-states - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: eservice.id, - descriptorId: descriptor.id, - }); - await updateDescriptorStateInTokenGenerationStatesTable( - eserviceId_descriptorId, - descriptor.state, - dynamoDBClient - ); } // flow for previous descriptor - if (!previousDescriptor) { - return Promise.resolve(); - } - const primaryKeyPrevious = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: descriptor.id, - }); - const existingCatalogEntryPrevious = await readCatalogEntry( - primaryKeyPrevious, - dynamoDBClient - ); if ( - existingCatalogEntryPrevious && - existingCatalogEntryPrevious.version > msg.version + !previousDescriptor || + previousDescriptor.state !== descriptorState.archived ) { return Promise.resolve(); - } else if ( - existingCatalogEntryPrevious && - existingCatalogEntryPrevious.version <= msg.version - ) { - await updateDescriptorStateInPlatformStatesEntry( - dynamoDBClient, - primaryKeyPrevious, - descriptorStateToClientState(previousDescriptor.state), - msg.version - ); + } else { + const primaryKeyPrevious = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: descriptor.id, + }); + + await deleteCatalogEntry(primaryKeyPrevious, dynamoDBClient); // token-generation-states const eserviceId_descriptorId_previous = @@ -161,12 +124,6 @@ export async function handleMessageV2( descriptor.state, dynamoDBClient ); - } else { - if (!existingCatalogEntryPrevious) { - throw genericInternalError( - `Unable to find catalog entry with PK ${primaryKeyPrevious}` - ); - } } }) .with( From 460ac9da8a24df58bcea3ad7fc6bde1bbe0f20cd Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 3 Sep 2024 11:15:26 +0200 Subject: [PATCH 077/241] Update test placeholders --- .../test/catalogPlatformstateWriter.integration.test.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 16a9eddfa2..c505f461c7 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -509,14 +509,7 @@ describe("database test", async () => { describe("EServiceDescriptorPublished (the previous descriptor becomes archived)", () => { // these tests start with the basic flow for the current descriptor (simple write operation). Then, additinal checks are added - it("no operation if the entry already exists: incoming has version 1; previous entry has version 2", () => { - expect(1).toBe(1); - }); - it("entry has to be updated: incoming has version 3; previous entry has version 2", () => { - expect(1).toBe(1); - }); - it("no previous entry", () => { - // to do throw error + it("entry has to be deleted", () => { expect(1).toBe(1); }); }); From 4a21c34b1c1dc9e22b21fb695281a85c90b97ff2 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 3 Sep 2024 11:41:44 +0200 Subject: [PATCH 078/241] Fix logic --- .../src/consumerServiceV1.ts | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index 7251fd311b..946e286e3d 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -48,22 +48,29 @@ export async function handleMessageV1( // fare query su platform states e vedere se c'è // se non c'è sono in draft, e continuo questa esecuzione // se c'è (presumibilmente come inactive) allora era suspended e sono nel caso sotto (sospensione e riattivazione hanno stesso handler) - const existingCatalogEntry = await readCatalogEntry( eserviceDescriptorPK, dynamoDBClient ); - if (!existingCatalogEntry) { - // the descriptor was draft so there was not an entry in platform-states - const catalogEntry: PlatformStatesCatalogEntry = { - PK: eserviceDescriptorPK, - state: descriptorStateToClientState(descriptor.state), - descriptorAudience: descriptor.audience[0], - version: msg.version, - updatedAt: new Date().toISOString(), - }; - await writeCatalogEntry(catalogEntry, dynamoDBClient); + if ( + existingCatalogEntry && + existingCatalogEntry.version > msg.version + ) { + return Promise.resolve(); + // Stops processing if the message is older than the catalog entry + } else if ( + existingCatalogEntry && + existingCatalogEntry.version <= msg.version + ) { + // suspended->published + + await updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, + eserviceDescriptorPK, + descriptorStateToClientState(descriptor.state), + msg.version + ); // token-generation-states const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ @@ -76,15 +83,15 @@ export async function handleMessageV1( dynamoDBClient ); } else { - // activation from suspended + const catalogEntry: PlatformStatesCatalogEntry = { + PK: eserviceDescriptorPK, + state: descriptorStateToClientState(descriptor.state), + descriptorAudience: descriptor.audience[0], + version: msg.version, + updatedAt: new Date().toISOString(), + }; - // platform-states - await updateDescriptorStateInPlatformStatesEntry( - dynamoDBClient, - existingCatalogEntry.PK, - descriptorStateToClientState(descriptor.state), - msg.version - ); + await writeCatalogEntry(catalogEntry, dynamoDBClient); // token-generation-states const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ From ef07d3bb4a921d79432b396b5a5ad8c2121bb412 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 3 Sep 2024 11:41:52 +0200 Subject: [PATCH 079/241] Add test placeholder --- .../test/catalogPlatformstateWriter.integration.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 1033279416..b7fbead53e 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -197,7 +197,7 @@ describe("database test", async () => { expect(retrievedEntry).toEqual(expectedEntry); }); - it("EServiceDescriptorUpdated (suspended -> published)", async () => { + it("EServiceDescriptorUpdated (suspended -> published, version of the event is newer)", async () => { const suspendedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], @@ -314,6 +314,10 @@ describe("database test", async () => { ); }); + it("EServiceDescriptorUpdated (published, no operation if version of the event is lower than existing entry)", async () => { + expect(1).toBe(1); + }); + it("EServiceDescriptorUpdated (published -> suspended)", async () => { const publishedDescriptor: Descriptor = { ...getMockDescriptor(), From 7124f2d1545ba931f85f0ffbeec6c77ce1fd8f62 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 3 Sep 2024 12:01:26 +0200 Subject: [PATCH 080/241] Add tests --- ...logPlatformstateWriter.integration.test.ts | 386 +++++++++++++++++- 1 file changed, 379 insertions(+), 7 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index c505f461c7..fe480ac937 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -146,8 +146,113 @@ describe("database test", async () => { describe("Events V2", async () => { const mockEService = getMockEService(); describe("EServiceDescriptorActivated", () => { - it("no operation if the entry already exists: incoming has version 1; previous entry has version 2", () => { - expect(1).toBe(1); + it("no operation if the entry already exists: incoming has version 1; previous entry has version 2", async () => { + const suspendedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.suspended, + publishedAt: new Date(), + suspendedAt: new Date(), + }; + const eservice: EService = { + ...mockEService, + descriptors: [suspendedDescriptor], + }; + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const publishedDescriptor: Descriptor = { + ...suspendedDescriptor, + publishedAt: new Date(), + suspendedAt: new Date(), + state: descriptorState.published, + }; + const updatedEService: EService = { + ...eservice, + descriptors: [publishedDescriptor], + }; + const payload: EServiceDescriptorActivatedV2 = { + eservice: toEServiceV2(updatedEService), + descriptorId: publishedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 1, + type: "EServiceDescriptorActivated", + event_version: 2, + data: payload, + log_date: new Date(), + }; + const catalogEntryPrimaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: catalogEntryPrimaryKey, + state: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + version: 2, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); + + // token-generation-states + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + + await sleep(1000, mockDate); + await handleMessageV2(message, dynamoDBClient); + + // platform-states + const retrievedCatalogEntry = await readCatalogEntry( + catalogEntryPrimaryKey, + dynamoDBClient + ); + + expect(retrievedCatalogEntry).toEqual(previousStateEntry); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + + expect(retrievedTokenStateEntries).toHaveLength(2); + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + previousTokenStateEntry1, + previousTokenStateEntry2, + ]) + ); }); it("entry has to be updated: incoming has version 3; previous entry has version 2", async () => { const suspendedDescriptor: Descriptor = { @@ -425,6 +530,9 @@ describe("database test", async () => { data: payload, log_date: new Date(), }; + + // TO DO token-generation-states? If the descriptor was draft, there were no entries in token-generation-states + await handleMessageV2(message, dynamoDBClient); const primaryKey = makePlatformStatesEServiceDescriptorPK({ @@ -503,20 +611,284 @@ describe("database test", async () => { expect(retrievedEntry).toEqual(previousStateEntry); }); it("entry has to be updated: incoming has version 3; previous entry has version 2", async () => { - expect(1).toBe(1); + const draftDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.published, + publishedAt: new Date(), + }; + const eservice: EService = { + ...mockEService, + descriptors: [draftDescriptor], + }; + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const publishedDescriptor: Descriptor = { + ...draftDescriptor, + publishedAt: new Date(), + state: descriptorState.archived, + }; + const updatedEService: EService = { + ...eservice, + descriptors: [publishedDescriptor], + }; + const payload: EServiceDescriptorArchivedV2 = { + eservice: toEServiceV2(updatedEService), + descriptorId: publishedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 3, + type: "EServiceDescriptorArchived", + event_version: 2, + data: payload, + log_date: new Date(), + }; + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: updatedEService.id, + descriptorId: publishedDescriptor.id, + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + version: 2, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); + + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + await sleep(1000, mockDate); + + await handleMessageV2(message, dynamoDBClient); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry1, + expectedTokenStateEntry2, + ]) + ); }); }); describe("EServiceDescriptorPublished (the previous descriptor becomes archived)", () => { // these tests start with the basic flow for the current descriptor (simple write operation). Then, additinal checks are added - it("entry has to be deleted", () => { - expect(1).toBe(1); + it("entry has to be deleted", async () => { + const previousPublishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.published, + version: "1", + }; + const draftDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.draft, + version: "2", + }; + const eservice: EService = { + ...mockEService, + descriptors: [previousPublishedDescriptor, draftDescriptor], + }; + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const archivedDescriptor: Descriptor = { + ...previousPublishedDescriptor, + archivedAt: new Date(), + state: descriptorState.archived, + }; + const publishedDescriptor: Descriptor = { + ...draftDescriptor, + publishedAt: new Date(), + state: descriptorState.published, + }; + const updatedEService: EService = { + ...eservice, + descriptors: [archivedDescriptor, publishedDescriptor], + }; + const payload: EServiceDescriptorPublishedV2 = { + eservice: toEServiceV2(updatedEService), + descriptorId: publishedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 2, + type: "EServiceDescriptorPublished", + event_version: 2, + data: payload, + log_date: new Date(), + }; + + await handleMessageV2(message, dynamoDBClient); + + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: updatedEService.id, + descriptorId: publishedDescriptor.id, + }); + const retrievedEntry = await readCatalogEntry( + primaryKey, + dynamoDBClient + ); + expect(retrievedEntry).toBeUndefined(); }); }); describe("EServiceDescriptorSuspended", () => { - it("no operation if the entry already exists: incoming has version 1; previous entry has version 2", () => { - expect(1).toBe(1); + it("no operation if the entry already exists: incoming has version 1; previous entry has version 2", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.published, + publishedAt: new Date(), + }; + const eservice: EService = { + ...mockEService, + descriptors: [publishedDescriptor], + }; + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const suspendedDescriptor: Descriptor = { + ...publishedDescriptor, + suspendedAt: new Date(), + state: descriptorState.suspended, + }; + const updatedEService: EService = { + ...eservice, + descriptors: [suspendedDescriptor], + }; + const payload: EServiceDescriptorSuspendedV2 = { + eservice: toEServiceV2(updatedEService), + descriptorId: suspendedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: mockEService.id, + version: 1, + type: "EServiceDescriptorSuspended", + event_version: 2, + data: payload, + log_date: new Date(), + }; + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: updatedEService.id, + descriptorId: publishedDescriptor.id, + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + version: 2, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); + + // token-generation-states + // TODO: replace last generateId() with kid + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + + await handleMessageV2(message, dynamoDBClient); + + const retrievedEntry = await readCatalogEntry( + primaryKey, + dynamoDBClient + ); + + expect(retrievedEntry).toEqual(previousStateEntry); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + + expect(retrievedTokenStateEntries).toHaveLength(2); + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + previousTokenStateEntry2, + previousTokenStateEntry1, + ]) + ); }); it("entry has to be updated: incoming has version 3; previous entry has version 2", async () => { const publishedDescriptor: Descriptor = { From 61e3392f3dcb948cf49bceeed75ac76c71accd4c Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 3 Sep 2024 12:34:34 +0200 Subject: [PATCH 081/241] Remove log --- .../catalog-platformstate-writer/src/utils.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index f173c9971b..a0ad09ff38 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -184,9 +184,7 @@ export const updateDescriptorStateInPlatformStatesEntry = async ( ReturnValues: "ALL_NEW", }; const command = new UpdateItemCommand(input); - console.log(command); - const a = await dynamoDBClient.send(command); - console.log(a); + await dynamoDBClient.send(command); }; export const writeTokenStateEntry = async ( @@ -250,18 +248,13 @@ export const writeTokenStateEntry = async ( TableName: config.tokenGenerationReadModelTableNameTokenGeneration, }; const command = new PutItemCommand(input); - console.log( - "tokenStateEntry.GSIPK_eserviceId_descriptorId ", - tokenStateEntry.GSIPK_eserviceId_descriptorId - ); - console.log("write token state", await dynamoDBClient.send(command)); + await dynamoDBClient.send(command); }; export const readTokenStateEntryByEServiceIdAndDescriptorId = async ( eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, dynamoDBClient: DynamoDBClient ): Promise => { - console.log("eserviceId_descriptorId ", eserviceId_descriptorId); const input: QueryInput = { TableName: config.tokenGenerationReadModelTableNameTokenGeneration, IndexName: "gsiIndex", // Use the name of your Global Secondary Index @@ -276,7 +269,6 @@ export const readTokenStateEntryByEServiceIdAndDescriptorId = async ( }; const command = new QueryCommand(input); const data: QueryCommandOutput = await dynamoDBClient.send(command); - console.log("data.Items ", data); if (!data.Items) { return undefined; @@ -300,7 +292,6 @@ export const readTokenStateEntriesByEserviceIdAndDescriptorId = async ( eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, dynamoDBClient: DynamoDBClient ): Promise => { - console.log("eserviceId_descriptorId ", eserviceId_descriptorId); const input: QueryInput = { TableName: config.tokenGenerationReadModelTableNameTokenGeneration, IndexName: "gsiIndex", // Use the name of your Global Secondary Index @@ -315,7 +306,7 @@ export const readTokenStateEntriesByEserviceIdAndDescriptorId = async ( }; const command = new QueryCommand(input); const data: QueryCommandOutput = await dynamoDBClient.send(command); - console.log("data.Items ", data); + // console.log("data.Items ", data); if (!data.Items) { return undefined; From ad23da6ce2d684f4be6ef6b00cde00d58f34ce6e Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 3 Sep 2024 12:35:28 +0200 Subject: [PATCH 082/241] Fix test --- ...logPlatformstateWriter.integration.test.ts | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index fe480ac937..a80955715d 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -111,8 +111,8 @@ describe("database test", async () => { ], }; const command2 = new CreateTableCommand(tokenGenerationTableDefinition); - const result = await dynamoDBClient.send(command2); - console.log(result); + await dynamoDBClient.send(command2); + // console.log(result); // const tablesResult = await dynamoDBClient.listTables(); // console.log(tablesResult.TableNames); @@ -615,7 +615,7 @@ describe("database test", async () => { ...getMockDescriptor(), audience: ["pagopa.it"], interface: getMockDocument(), - state: descriptorState.published, + state: descriptorState.draft, publishedAt: new Date(), }; const eservice: EService = { @@ -627,7 +627,7 @@ describe("database test", async () => { const publishedDescriptor: Descriptor = { ...draftDescriptor, publishedAt: new Date(), - state: descriptorState.archived, + state: descriptorState.published, }; const updatedEService: EService = { ...eservice, @@ -641,7 +641,7 @@ describe("database test", async () => { sequence_num: 1, stream_id: mockEService.id, version: 3, - type: "EServiceDescriptorArchived", + type: "EServiceDescriptorPublished", event_version: 2, data: payload, log_date: new Date(), @@ -658,7 +658,6 @@ describe("database test", async () => { updatedAt: new Date().toISOString(), }; await writeCatalogEntry(previousStateEntry, dynamoDBClient); - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, descriptorId: publishedDescriptor.id, @@ -674,7 +673,7 @@ describe("database test", async () => { const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), - descriptorState: itemState.active, + descriptorState: itemState.inactive, descriptorAudience: publishedDescriptor.audience[0], GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; @@ -683,7 +682,7 @@ describe("database test", async () => { const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), - descriptorState: itemState.active, + descriptorState: itemState.inactive, descriptorAudience: publishedDescriptor.audience[0], GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; @@ -701,19 +700,19 @@ describe("database test", async () => { const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { ...previousTokenStateEntry1, - descriptorState: itemState.inactive, + descriptorState: itemState.active, updatedAt: new Date().toISOString(), }; const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { ...previousTokenStateEntry2, - descriptorState: itemState.inactive, + descriptorState: itemState.active, updatedAt: new Date().toISOString(), }; expect(retrievedTokenStateEntries).toEqual( expect.arrayContaining([ - expectedTokenStateEntry1, expectedTokenStateEntry2, + expectedTokenStateEntry1, ]) ); }); From 237e23f21b14931ac46e3f997b550aa3caf5e47d Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 3 Sep 2024 14:07:05 +0200 Subject: [PATCH 083/241] Simplify tests --- ...logPlatformstateWriter.integration.test.ts | 265 ++++++------------ 1 file changed, 86 insertions(+), 179 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index a80955715d..13a1d65638 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -52,7 +52,7 @@ import { import { handleMessageV2 } from "../src/consumerServiceV2.js"; import { config } from "./utils.js"; -describe("database test", async () => { +describe("integration tests", async () => { if (!config) { fail(); } @@ -144,40 +144,26 @@ describe("database test", async () => { }); describe("Events V2", async () => { - const mockEService = getMockEService(); describe("EServiceDescriptorActivated", () => { it("no operation if the entry already exists: incoming has version 1; previous entry has version 2", async () => { - const suspendedDescriptor: Descriptor = { + const publishedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], interface: getMockDocument(), - state: descriptorState.suspended, + state: descriptorState.published, publishedAt: new Date(), - suspendedAt: new Date(), }; const eservice: EService = { - ...mockEService, - descriptors: [suspendedDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const publishedDescriptor: Descriptor = { - ...suspendedDescriptor, - publishedAt: new Date(), - suspendedAt: new Date(), - state: descriptorState.published, - }; - const updatedEService: EService = { - ...eservice, + ...getMockEService(), descriptors: [publishedDescriptor], }; const payload: EServiceDescriptorActivatedV2 = { - eservice: toEServiceV2(updatedEService), + eservice: toEServiceV2(eservice), descriptorId: publishedDescriptor.id, }; const message: EServiceEventEnvelope = { sequence_num: 1, - stream_id: mockEService.id, + stream_id: eservice.id, version: 1, type: "EServiceDescriptorActivated", event_version: 2, @@ -255,37 +241,25 @@ describe("database test", async () => { ); }); it("entry has to be updated: incoming has version 3; previous entry has version 2", async () => { - const suspendedDescriptor: Descriptor = { + const publishedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], interface: getMockDocument(), - state: descriptorState.suspended, + state: descriptorState.published, publishedAt: new Date(), - suspendedAt: new Date(), }; const eservice: EService = { - ...mockEService, - descriptors: [suspendedDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const publishedDescriptor: Descriptor = { - ...suspendedDescriptor, - publishedAt: new Date(), - suspendedAt: new Date(), - state: descriptorState.published, - }; - const updatedEService: EService = { - ...eservice, + ...getMockEService(), descriptors: [publishedDescriptor], }; + const payload: EServiceDescriptorActivatedV2 = { - eservice: toEServiceV2(updatedEService), + eservice: toEServiceV2(eservice), descriptorId: publishedDescriptor.id, }; const message: EServiceEventEnvelope = { sequence_num: 1, - stream_id: mockEService.id, + stream_id: eservice.id, version: 2, type: "EServiceDescriptorActivated", event_version: 2, @@ -384,35 +358,26 @@ describe("database test", async () => { }); it("EServiceDescriptorArchived", async () => { - const publishedDescriptor: Descriptor = { + const archivedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], interface: getMockDocument(), - state: descriptorState.published, + state: descriptorState.archived, publishedAt: new Date(), - }; - const eservice: EService = { - ...mockEService, - descriptors: [publishedDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const archivedDescriptor: Descriptor = { - ...publishedDescriptor, archivedAt: new Date(), - state: descriptorState.archived, }; - const updatedEService: EService = { - ...eservice, + const eservice: EService = { + ...getMockEService(), descriptors: [archivedDescriptor], }; + const payload: EServiceDescriptorArchivedV2 = { - eservice: toEServiceV2(updatedEService), + eservice: toEServiceV2(eservice), descriptorId: archivedDescriptor.id, }; const message: EServiceEventEnvelope = { sequence_num: 1, - stream_id: mockEService.id, + stream_id: eservice.id, version: 2, type: "EServiceDescriptorArchived", event_version: 2, @@ -420,13 +385,13 @@ describe("database test", async () => { log_date: new Date(), }; const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: updatedEService.id, - descriptorId: publishedDescriptor.id, + eserviceId: eservice.id, + descriptorId: archivedDescriptor.id, }); const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, state: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], + descriptorAudience: archivedDescriptor.audience[0], version: 1, updatedAt: new Date().toISOString(), }; @@ -434,7 +399,7 @@ describe("database test", async () => { const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, + descriptorId: archivedDescriptor.id, }); const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), @@ -448,7 +413,7 @@ describe("database test", async () => { { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), descriptorState: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], + descriptorAudience: archivedDescriptor.audience[0], GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); @@ -457,7 +422,7 @@ describe("database test", async () => { { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), descriptorState: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], + descriptorAudience: archivedDescriptor.audience[0], GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); @@ -496,34 +461,25 @@ describe("database test", async () => { describe("EServiceDescriptorPublished (the eservice has 1 descriptor)", () => { it("no previous entry", async () => { - const draftDescriptor: Descriptor = { + const publishedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], interface: getMockDocument(), - state: descriptorState.draft, - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const publishedDescriptor: Descriptor = { - ...draftDescriptor, - publishedAt: new Date(), state: descriptorState.published, + publishedAt: new Date(), }; - const updatedEService: EService = { - ...eservice, + const eservice: EService = { + ...getMockEService(), descriptors: [publishedDescriptor], }; + const payload: EServiceDescriptorPublishedV2 = { - eservice: toEServiceV2(updatedEService), + eservice: toEServiceV2(eservice), descriptorId: publishedDescriptor.id, }; const message: EServiceEventEnvelope = { sequence_num: 1, - stream_id: mockEService.id, + stream_id: eservice.id, version: 2, type: "EServiceDescriptorPublished", event_version: 2, @@ -536,7 +492,7 @@ describe("database test", async () => { await handleMessageV2(message, dynamoDBClient); const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: updatedEService.id, + eserviceId: eservice.id, descriptorId: publishedDescriptor.id, }); const retrievedEntry = await readCatalogEntry( @@ -555,34 +511,25 @@ describe("database test", async () => { // TODO: add test with incoming version 1 and previous entry version 1? it("no operation if the entry already exists. Incoming has version 1; previous entry has version 2", async () => { - const draftDescriptor: Descriptor = { + const publishedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], interface: getMockDocument(), - state: descriptorState.draft, - }; - const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const publishedDescriptor: Descriptor = { - ...draftDescriptor, - publishedAt: new Date(), state: descriptorState.published, + publishedAt: new Date(), }; - const updatedEService: EService = { - ...eservice, + const eservice: EService = { + ...getMockEService(), descriptors: [publishedDescriptor], }; + const payload: EServiceDescriptorPublishedV2 = { - eservice: toEServiceV2(updatedEService), + eservice: toEServiceV2(eservice), descriptorId: publishedDescriptor.id, }; const message: EServiceEventEnvelope = { sequence_num: 1, - stream_id: mockEService.id, + stream_id: eservice.id, version: 1, type: "EServiceDescriptorPublished", event_version: 2, @@ -591,7 +538,7 @@ describe("database test", async () => { }; const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: updatedEService.id, + eserviceId: eservice.id, descriptorId: publishedDescriptor.id, }); const previousStateEntry: PlatformStatesCatalogEntry = { @@ -611,35 +558,25 @@ describe("database test", async () => { expect(retrievedEntry).toEqual(previousStateEntry); }); it("entry has to be updated: incoming has version 3; previous entry has version 2", async () => { - const draftDescriptor: Descriptor = { + const publishedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], interface: getMockDocument(), - state: descriptorState.draft, + state: descriptorState.published, publishedAt: new Date(), }; const eservice: EService = { - ...mockEService, - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const publishedDescriptor: Descriptor = { - ...draftDescriptor, - publishedAt: new Date(), - state: descriptorState.published, - }; - const updatedEService: EService = { - ...eservice, + ...getMockEService(), descriptors: [publishedDescriptor], }; + const payload: EServiceDescriptorArchivedV2 = { - eservice: toEServiceV2(updatedEService), + eservice: toEServiceV2(eservice), descriptorId: publishedDescriptor.id, }; const message: EServiceEventEnvelope = { sequence_num: 1, - stream_id: mockEService.id, + stream_id: eservice.id, version: 3, type: "EServiceDescriptorPublished", event_version: 2, @@ -647,7 +584,7 @@ describe("database test", async () => { log_date: new Date(), }; const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: updatedEService.id, + eserviceId: eservice.id, descriptorId: publishedDescriptor.id, }); const previousStateEntry: PlatformStatesCatalogEntry = { @@ -721,47 +658,35 @@ describe("database test", async () => { describe("EServiceDescriptorPublished (the previous descriptor becomes archived)", () => { // these tests start with the basic flow for the current descriptor (simple write operation). Then, additinal checks are added it("entry has to be deleted", async () => { - const previousPublishedDescriptor: Descriptor = { + const archivedDescriptor: Descriptor = { ...getMockDescriptor(), + state: descriptorState.archived, audience: ["pagopa.it"], interface: getMockDocument(), - state: descriptorState.published, version: "1", + publishedAt: new Date(), + archivedAt: new Date(), }; - const draftDescriptor: Descriptor = { + const publishedDescriptor: Descriptor = { ...getMockDescriptor(), + archivedAt: new Date(), + state: descriptorState.published, audience: ["pagopa.it"], interface: getMockDocument(), - state: descriptorState.draft, version: "2", }; - const eservice: EService = { - ...mockEService, - descriptors: [previousPublishedDescriptor, draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - const archivedDescriptor: Descriptor = { - ...previousPublishedDescriptor, - archivedAt: new Date(), - state: descriptorState.archived, - }; - const publishedDescriptor: Descriptor = { - ...draftDescriptor, - publishedAt: new Date(), - state: descriptorState.published, - }; - const updatedEService: EService = { - ...eservice, + const eservice: EService = { + ...getMockEService(), descriptors: [archivedDescriptor, publishedDescriptor], }; const payload: EServiceDescriptorPublishedV2 = { - eservice: toEServiceV2(updatedEService), + eservice: toEServiceV2(eservice), descriptorId: publishedDescriptor.id, }; const message: EServiceEventEnvelope = { sequence_num: 1, - stream_id: mockEService.id, + stream_id: eservice.id, version: 2, type: "EServiceDescriptorPublished", event_version: 2, @@ -772,7 +697,7 @@ describe("database test", async () => { await handleMessageV2(message, dynamoDBClient); const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: updatedEService.id, + eserviceId: eservice.id, descriptorId: publishedDescriptor.id, }); const retrievedEntry = await readCatalogEntry( @@ -785,35 +710,26 @@ describe("database test", async () => { describe("EServiceDescriptorSuspended", () => { it("no operation if the entry already exists: incoming has version 1; previous entry has version 2", async () => { - const publishedDescriptor: Descriptor = { + const suspendedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], interface: getMockDocument(), - state: descriptorState.published, + state: descriptorState.suspended, publishedAt: new Date(), - }; - const eservice: EService = { - ...mockEService, - descriptors: [publishedDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const suspendedDescriptor: Descriptor = { - ...publishedDescriptor, suspendedAt: new Date(), - state: descriptorState.suspended, }; - const updatedEService: EService = { - ...eservice, + const eservice: EService = { + ...getMockEService(), descriptors: [suspendedDescriptor], }; + const payload: EServiceDescriptorSuspendedV2 = { - eservice: toEServiceV2(updatedEService), + eservice: toEServiceV2(eservice), descriptorId: suspendedDescriptor.id, }; const message: EServiceEventEnvelope = { sequence_num: 1, - stream_id: mockEService.id, + stream_id: eservice.id, version: 1, type: "EServiceDescriptorSuspended", event_version: 2, @@ -821,13 +737,13 @@ describe("database test", async () => { log_date: new Date(), }; const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: updatedEService.id, - descriptorId: publishedDescriptor.id, + eserviceId: eservice.id, + descriptorId: suspendedDescriptor.id, }); const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, state: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], + descriptorAudience: suspendedDescriptor.audience[0], version: 2, updatedAt: new Date().toISOString(), }; @@ -841,13 +757,13 @@ describe("database test", async () => { }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, + descriptorId: suspendedDescriptor.id, }); const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), descriptorState: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], + descriptorAudience: suspendedDescriptor.audience[0], GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); @@ -860,7 +776,7 @@ describe("database test", async () => { { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), descriptorState: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], + descriptorAudience: suspendedDescriptor.audience[0], GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); @@ -890,35 +806,26 @@ describe("database test", async () => { ); }); it("entry has to be updated: incoming has version 3; previous entry has version 2", async () => { - const publishedDescriptor: Descriptor = { + const suspendedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], interface: getMockDocument(), - state: descriptorState.published, + state: descriptorState.suspended, publishedAt: new Date(), - }; - const eservice: EService = { - ...mockEService, - descriptors: [publishedDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const suspendedDescriptor: Descriptor = { - ...publishedDescriptor, suspendedAt: new Date(), - state: descriptorState.suspended, }; - const updatedEService: EService = { - ...eservice, + + const eservice: EService = { + ...getMockEService(), descriptors: [suspendedDescriptor], }; const payload: EServiceDescriptorSuspendedV2 = { - eservice: toEServiceV2(updatedEService), + eservice: toEServiceV2(eservice), descriptorId: suspendedDescriptor.id, }; const message: EServiceEventEnvelope = { sequence_num: 1, - stream_id: mockEService.id, + stream_id: eservice.id, version: 2, type: "EServiceDescriptorSuspended", event_version: 2, @@ -926,13 +833,13 @@ describe("database test", async () => { log_date: new Date(), }; const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: updatedEService.id, - descriptorId: publishedDescriptor.id, + eserviceId: eservice.id, + descriptorId: suspendedDescriptor.id, }); const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, state: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], + descriptorAudience: suspendedDescriptor.audience[0], version: 1, updatedAt: new Date().toISOString(), }; @@ -946,13 +853,13 @@ describe("database test", async () => { }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, + descriptorId: suspendedDescriptor.id, }); const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), descriptorState: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], + descriptorAudience: suspendedDescriptor.audience[0], GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); @@ -965,7 +872,7 @@ describe("database test", async () => { { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), descriptorState: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], + descriptorAudience: suspendedDescriptor.audience[0], GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); From 93d6f4a7d34f056a527aa8de29cc88c578ee6221 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Tue, 3 Sep 2024 14:19:15 +0200 Subject: [PATCH 084/241] Add test for EServiceDescriptorUpdated published --- ...logPlatformstateWriter.integration.test.ts | 108 +++++++++++++++++- 1 file changed, 104 insertions(+), 4 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index d8221c96fd..029b2aabe6 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -160,6 +160,7 @@ describe("integration tests", async () => { }; // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + // TODO: remove draftDescriptor const publishedDescriptor: Descriptor = { ...draftDescriptor, publishedAt: new Date(), @@ -279,13 +280,16 @@ describe("integration tests", async () => { await handleMessageV1(message, dynamoDBClient); await sleep(1000, mockDate); - const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); - const expectedEntry: PlatformStatesCatalogEntry = { + const retrievedCatalogEntry = await readCatalogEntry( + primaryKey, + dynamoDBClient + ); + const expectedCatalogEntry: PlatformStatesCatalogEntry = { ...previousStateEntry, state: itemState.active, version: 2, }; - expect(retrievedEntry).toEqual(expectedEntry); + expect(retrievedCatalogEntry).toEqual(expectedCatalogEntry); // token-generation-states const retrievedTokenStateEntries = @@ -315,7 +319,103 @@ describe("integration tests", async () => { }); it("EServiceDescriptorUpdated (published, no operation if version of the event is lower than existing entry)", async () => { - expect(1).toBe(1); + console.log( + "EServiceDescriptorUpdated (published, no operation if version of the event is lower than existing entry)" + ); + const previousDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + // previous state could be anything + state: descriptorState.published, + publishedAt: new Date(), + suspendedAt: new Date(), + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [previousDescriptor], + }; + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const payload: EServiceDescriptorUpdatedV1 = { + eserviceId: eservice.id, + eserviceDescriptor: toDescriptorV1(previousDescriptor), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 1, + type: "EServiceDescriptorUpdated", + event_version: 1, + data: payload, + log_date: new Date(), + }; + const catalogPrimaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: previousDescriptor.id, + }); + const previousCatalogStateEntry: PlatformStatesCatalogEntry = { + PK: catalogPrimaryKey, + state: itemState.inactive, + descriptorAudience: previousDescriptor.audience[0], + version: 2, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousCatalogStateEntry, dynamoDBClient); + + // token-generation-states + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: previousDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: previousDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: previousDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + + await handleMessageV1(message, dynamoDBClient); + await sleep(1000, mockDate); + + const retrievedCatalogEntry = await readCatalogEntry( + catalogPrimaryKey, + dynamoDBClient + ); + expect(retrievedCatalogEntry).toEqual(previousCatalogStateEntry); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + console.log(previousTokenStateEntry1, previousTokenStateEntry2); + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + previousTokenStateEntry1, + previousTokenStateEntry2, + ]) + ); }); it("EServiceDescriptorUpdated (published -> suspended)", async () => { From 76b88eae4c45d77d50d66444937fd19000177a92 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 3 Sep 2024 14:29:39 +0200 Subject: [PATCH 085/241] Use key types --- packages/catalog-platformstate-writer/src/utils.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index a0ad09ff38..5998a9fef0 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -8,6 +8,7 @@ import { itemState, ItemState, PlatformStatesCatalogEntry, + PlatformStatesEServiceDescriptorPK, TokenGenerationStatesClientPurposeEntry, } from "pagopa-interop-models"; import { @@ -62,7 +63,7 @@ export const writeCatalogEntry = async ( }; export const readCatalogEntry = async ( - primaryKey: string, + primaryKey: PlatformStatesEServiceDescriptorPK, dynamoDBClient: DynamoDBClient ): Promise => { const input: GetItemInput = { @@ -92,7 +93,7 @@ export const readCatalogEntry = async ( }; export const deleteCatalogEntry = async ( - primaryKey: string, + primaryKey: PlatformStatesEServiceDescriptorPK, dynamoDBClient: DynamoDBClient ): Promise => { const input: DeleteItemInput = { @@ -126,7 +127,7 @@ export const descriptorStateToClientState = ( // TO DO scrivere test per vedere se funziona come upsert o solo come update export const updateDescriptorState = async ( dynamoDBClient: DynamoDBClient, - primaryKey: string, + primaryKey: PlatformStatesEServiceDescriptorPK, state: ItemState ): Promise => { const input: UpdateItemInput = { @@ -154,7 +155,7 @@ export const updateDescriptorState = async ( export const updateDescriptorStateInPlatformStatesEntry = async ( dynamoDBClient: DynamoDBClient, - primaryKey: string, + primaryKey: PlatformStatesEServiceDescriptorPK, state: ItemState, version: number ): Promise => { From 9c13548eb31176dc49e24200a91b3fdc9a98376f Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Tue, 3 Sep 2024 14:41:09 +0200 Subject: [PATCH 086/241] Fix primaryKey type --- .../catalog-platformstate-writer/src/consumerServiceV1.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index 946e286e3d..30b4b9ea21 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -136,7 +136,10 @@ export async function handleMessageV1( const descriptor = fromDescriptorV1(descriptorV1); // platform-states - const primaryKey = `ESERVICEDESCRIPTOR#${eserviceId}#${descriptor.id}`; + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId, + descriptorId: descriptor.id, + }); await deleteCatalogEntry(primaryKey, dynamoDBClient); // token-generation-states From 49f9651f3124c14f6ff0cbd61daa171efa929f5a Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Tue, 3 Sep 2024 15:41:05 +0200 Subject: [PATCH 087/241] Improvements --- .../catalog-platformstate-writer/src/utils.ts | 80 ++++----- ...logPlatformstateWriter.integration.test.ts | 167 ++++++++++++++++++ 2 files changed, 201 insertions(+), 46 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index 38aac7004a..73627a40f6 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -23,9 +23,6 @@ import { QueryCommand, QueryCommandOutput, QueryInput, - ScanCommand, - ScanCommandOutput, - ScanInput, UpdateItemCommand, UpdateItemInput, } from "@aws-sdk/client-dynamodb"; @@ -39,6 +36,7 @@ export const writeCatalogEntry = async ( dynamoDBClient: DynamoDBClient ): Promise => { const input: PutItemInput = { + ConditionExpression: "attribute_not_exists(PK)", Item: { PK: { S: catalogEntry.PK, @@ -106,6 +104,7 @@ export const deleteCatalogEntry = async ( await dynamoDBClient.send(command); }; +/* export const readAllItems = async ( dynamoDBClient: DynamoDBClient ): Promise => { @@ -116,6 +115,7 @@ export const readAllItems = async ( const read: ScanCommandOutput = await dynamoDBClient.send(commandQuery); return read; }; +*/ export const descriptorStateToClientState = ( state: DescriptorState @@ -124,35 +124,6 @@ export const descriptorStateToClientState = ( ? itemState.active : itemState.inactive; -// TO DO scrivere test per vedere se funziona come upsert o solo come update -export const updateDescriptorState = async ( - dynamoDBClient: DynamoDBClient, - primaryKey: PlatformStatesEServiceDescriptorPK, - state: ItemState -): Promise => { - const input: UpdateItemInput = { - Key: { - PK: { - S: primaryKey, - }, - }, - ExpressionAttributeValues: { - ":newState": { - S: state, - }, - ":newUpdateAt": { - S: new Date().toISOString(), - }, - }, - UpdateExpression: - "SET descriptorState = :newState, updatedAt = :newUpdateAt", - TableName: config.tokenGenerationReadModelTableNameTokenGeneration, - ReturnValues: "ALL_NEW", - }; - const command = new UpdateItemCommand(input); - await dynamoDBClient.send(command); -}; - export const updateDescriptorStateInPlatformStatesEntry = async ( dynamoDBClient: DynamoDBClient, primaryKey: PlatformStatesEServiceDescriptorPK, @@ -160,6 +131,7 @@ export const updateDescriptorStateInPlatformStatesEntry = async ( version: number ): Promise => { const input: UpdateItemInput = { + ConditionExpression: "attribute_exists(PK)", Key: { PK: { S: primaryKey, @@ -193,6 +165,7 @@ export const writeTokenStateEntry = async ( dynamoDBClient: DynamoDBClient ): Promise => { const input: PutItemInput = { + ConditionExpression: "attribute_not_exists(PK)", Item: { PK: { S: tokenStateEntry.PK, @@ -252,6 +225,7 @@ export const writeTokenStateEntry = async ( await dynamoDBClient.send(command); }; +/* export const readTokenStateEntryByEServiceIdAndDescriptorId = async ( eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, dynamoDBClient: DynamoDBClient @@ -285,11 +259,12 @@ export const readTokenStateEntryByEServiceIdAndDescriptorId = async ( return tokenStateEntry.data; } }; +*/ export const readTokenStateEntriesByEserviceIdAndDescriptorId = async ( eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, dynamoDBClient: DynamoDBClient -): Promise => { +): Promise => { const input: QueryInput = { TableName: config.tokenGenerationReadModelTableNameTokenGeneration, IndexName: "gsiIndex", // Use the name of your Global Secondary Index @@ -307,7 +282,9 @@ export const readTokenStateEntriesByEserviceIdAndDescriptorId = async ( // console.log("data.Items ", data); if (!data.Items) { - return undefined; + throw genericInternalError( + `Unable to read token state entries: result ${JSON.stringify(data)} ` + ); } else { const unmarshalledItems = data.Items.map((item) => unmarshall(item)); @@ -345,17 +322,28 @@ export const updateDescriptorStateInTokenGenerationStatesTable = async ( dynamoDBClient ); - if (entriesToUpdate) { - for (const entry of entriesToUpdate) { - await updateDescriptorState( - dynamoDBClient, - entry.PK, - descriptorStateToClientState(descriptorState) - ); - } - } else { - throw genericInternalError( - `Unable to find token generation state entry with GSIPK_eserviceId_descriptorId ${eserviceId_descriptorId}` - ); + for (const entry of entriesToUpdate) { + const input: UpdateItemInput = { + ConditionExpression: "attribute_exists(PK)", + Key: { + PK: { + S: entry.PK, + }, + }, + ExpressionAttributeValues: { + ":newState": { + S: descriptorStateToClientState(descriptorState), + }, + ":newUpdateAt": { + S: new Date().toISOString(), + }, + }, + UpdateExpression: + "SET descriptorState = :newState, updatedAt = :newUpdateAt", + TableName: config.tokenGenerationReadModelTableNameTokenGeneration, + ReturnValues: "ALL_NEW", + }; + const command = new UpdateItemCommand(input); + await dynamoDBClient.send(command); } }; diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 029b2aabe6..dd3cfc7476 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-floating-promises */ /* eslint-disable no-console */ import { fail } from "assert"; import { @@ -31,6 +32,7 @@ import { toEServiceV2, } from "pagopa-interop-models"; import { + ConditionalCheckFailedException, CreateTableCommand, CreateTableInput, DeleteTableCommand, @@ -46,9 +48,11 @@ import { } from "pagopa-interop-commons-test"; import { handleMessageV1 } from "../src/consumerServiceV1.js"; import { + descriptorStateToClientState, readCatalogEntry, readTokenStateEntriesByEserviceIdAndDescriptorId, sleep, + updateDescriptorStateInPlatformStatesEntry, writeCatalogEntry, writeTokenStateEntry, } from "../src/utils.js"; @@ -146,6 +150,169 @@ describe("integration tests", async () => { vi.useRealTimers(); }); + describe("utils", async () => { + // TODO: move this to other test file after improving table setup + describe("updateDescriptorStateInPlatformStatesEntry", async () => { + it("should throw error if previous entry doesn't exist", async () => { + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + expect( + updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, + primaryKey, + itemState.active, + 1 + ) + ).rejects.toThrowError(ConditionalCheckFailedException); + const catalogEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + expect(catalogEntry).toBeUndefined(); + }); + + it("should update state if previous entry exists", async () => { + expect(1).toBe(1); + }); + }); + + describe("writeCatalogEntry", async () => { + it("should throw error if previous entry exists", async () => { + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.inactive, + descriptorAudience: "pagopa.it", + version: 1, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); + expect( + writeCatalogEntry(previousStateEntry, dynamoDBClient) + ).rejects.toThrowError(ConditionalCheckFailedException); + }); + + it("should write if previous entry doesn't exist", async () => { + expect(1).toBe(1); + }); + }); + + describe("readCatalogEntry", async () => { + it("should return undefined if entry doesn't exist", async () => { + expect(1).toBe(1); + }); + + it("should return entry if it exists", async () => { + expect(1).toBe(1); + }); + }); + + describe("deleteCatalogEntry", async () => { + it("should not throw error if previous entry doesn't exist", async () => { + expect(1).toBe(1); + }); + + it("should delete the entry if it exists", async () => { + expect(1).toBe(1); + }); + }); + + describe("descriptorStateToClientState", async () => { + it.each([descriptorState.published, descriptorState.deprecated])( + "should convert %s state to active", + async (s) => { + expect(descriptorStateToClientState(s)).toBe(itemState.active); + } + ); + + it.each([ + descriptorState.archived, + descriptorState.draft, + descriptorState.suspended, + ])("should convert %s state to inactive", async (s) => { + expect(descriptorStateToClientState(s)).toBe(itemState.inactive); + }); + }); + + // token-generation-states + describe("writeTokenStateEntry", async () => { + it("should throw error if previous entry exists", async () => { + expect(1).toBe(1); + }); + + it("should write if previous entry doesn't exist", async () => { + expect(1).toBe(1); + }); + }); + + describe("readTokenStateEntriesByEserviceIdAndDescriptorId", async () => { + // TODO: undefined? + it("should return empty string if entries do not exist", async () => { + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const result = await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + expect(result).toEqual([]); + }); + + it("should return entries if they exist", async () => { + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const tokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: "pagopa.it", + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(tokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const tokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: "pagopa.it", + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(tokenStateEntry2, dynamoDBClient); + + const tokenEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + + expect(tokenEntries).toEqual( + expect.arrayContaining([tokenStateEntry1, tokenStateEntry2]) + ); + }); + }); + + describe("updateDescriptorStateInTokenGenerationStatesTable", async () => { + it("should throw error if previous entry doesn't exist", async () => { + expect(1).toBe(1); + }); + + it("should update state if previous entry exists", async () => { + expect(1).toBe(1); + }); + }); + }); + describe("Events V1", async () => { it("EServiceDescriptorUpdated (draft -> published)", async () => { const draftDescriptor: Descriptor = { From aa7a31092b84c40d1d396f66ab54c869834d5711 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Tue, 3 Sep 2024 15:51:32 +0200 Subject: [PATCH 088/241] Add utils tests --- ...logPlatformstateWriter.integration.test.ts | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 13a1d65638..b51dc7f450 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-floating-promises */ /* eslint-disable no-console */ import { fail } from "assert"; import { @@ -30,6 +31,7 @@ import { toEServiceV2, } from "pagopa-interop-models"; import { + ConditionalCheckFailedException, CreateTableCommand, CreateTableInput, DeleteTableCommand, @@ -43,9 +45,11 @@ import { getMockTokenStatesClientPurposeEntry, } from "pagopa-interop-commons-test"; import { + descriptorStateToClientState, readCatalogEntry, readTokenStateEntriesByEserviceIdAndDescriptorId, sleep, + updateDescriptorStateInPlatformStatesEntry, writeCatalogEntry, writeTokenStateEntry, } from "../src/utils.js"; @@ -143,6 +147,169 @@ describe("integration tests", async () => { vi.useRealTimers(); }); + describe("utils", async () => { + // TODO: move this to other test file after improving table setup + describe("updateDescriptorStateInPlatformStatesEntry", async () => { + it("should throw error if previous entry doesn't exist", async () => { + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + expect( + updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, + primaryKey, + itemState.active, + 1 + ) + ).rejects.toThrowError(ConditionalCheckFailedException); + const catalogEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + expect(catalogEntry).toBeUndefined(); + }); + + it("should update state if previous entry exists", async () => { + expect(1).toBe(1); + }); + }); + + describe("writeCatalogEntry", async () => { + it("should throw error if previous entry exists", async () => { + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.inactive, + descriptorAudience: "pagopa.it", + version: 1, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); + expect( + writeCatalogEntry(previousStateEntry, dynamoDBClient) + ).rejects.toThrowError(ConditionalCheckFailedException); + }); + + it("should write if previous entry doesn't exist", async () => { + expect(1).toBe(1); + }); + }); + + describe("readCatalogEntry", async () => { + it("should return undefined if entry doesn't exist", async () => { + expect(1).toBe(1); + }); + + it("should return entry if it exists", async () => { + expect(1).toBe(1); + }); + }); + + describe("deleteCatalogEntry", async () => { + it("should not throw error if previous entry doesn't exist", async () => { + expect(1).toBe(1); + }); + + it("should delete the entry if it exists", async () => { + expect(1).toBe(1); + }); + }); + + describe("descriptorStateToClientState", async () => { + it.each([descriptorState.published, descriptorState.deprecated])( + "should convert %s state to active", + async (s) => { + expect(descriptorStateToClientState(s)).toBe(itemState.active); + } + ); + + it.each([ + descriptorState.archived, + descriptorState.draft, + descriptorState.suspended, + ])("should convert %s state to inactive", async (s) => { + expect(descriptorStateToClientState(s)).toBe(itemState.inactive); + }); + }); + + // token-generation-states + describe("writeTokenStateEntry", async () => { + it("should throw error if previous entry exists", async () => { + expect(1).toBe(1); + }); + + it("should write if previous entry doesn't exist", async () => { + expect(1).toBe(1); + }); + }); + + describe("readTokenStateEntriesByEserviceIdAndDescriptorId", async () => { + // TODO: undefined? + it("should return empty string if entries do not exist", async () => { + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const result = await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + expect(result).toEqual([]); + }); + + it("should return entries if they exist", async () => { + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const tokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: "pagopa.it", + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(tokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const tokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: "pagopa.it", + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(tokenStateEntry2, dynamoDBClient); + + const tokenEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + + expect(tokenEntries).toEqual( + expect.arrayContaining([tokenStateEntry1, tokenStateEntry2]) + ); + }); + }); + + describe("updateDescriptorStateInTokenGenerationStatesTable", async () => { + it("should throw error if previous entry doesn't exist", async () => { + expect(1).toBe(1); + }); + + it("should update state if previous entry exists", async () => { + expect(1).toBe(1); + }); + }); + }); + describe("Events V2", async () => { describe("EServiceDescriptorActivated", () => { it("no operation if the entry already exists: incoming has version 1; previous entry has version 2", async () => { From 1fa67b6d899a743d28845ee95e3cde9cecbdc6a9 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Tue, 3 Sep 2024 15:52:27 +0200 Subject: [PATCH 089/241] Update utils --- .../catalog-platformstate-writer/src/utils.ts | 87 ++++++++----------- 1 file changed, 36 insertions(+), 51 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index 5998a9fef0..73627a40f6 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -23,9 +23,6 @@ import { QueryCommand, QueryCommandOutput, QueryInput, - ScanCommand, - ScanCommandOutput, - ScanInput, UpdateItemCommand, UpdateItemInput, } from "@aws-sdk/client-dynamodb"; @@ -39,6 +36,7 @@ export const writeCatalogEntry = async ( dynamoDBClient: DynamoDBClient ): Promise => { const input: PutItemInput = { + ConditionExpression: "attribute_not_exists(PK)", Item: { PK: { S: catalogEntry.PK, @@ -106,6 +104,7 @@ export const deleteCatalogEntry = async ( await dynamoDBClient.send(command); }; +/* export const readAllItems = async ( dynamoDBClient: DynamoDBClient ): Promise => { @@ -116,6 +115,7 @@ export const readAllItems = async ( const read: ScanCommandOutput = await dynamoDBClient.send(commandQuery); return read; }; +*/ export const descriptorStateToClientState = ( state: DescriptorState @@ -124,35 +124,6 @@ export const descriptorStateToClientState = ( ? itemState.active : itemState.inactive; -// TO DO scrivere test per vedere se funziona come upsert o solo come update -export const updateDescriptorState = async ( - dynamoDBClient: DynamoDBClient, - primaryKey: PlatformStatesEServiceDescriptorPK, - state: ItemState -): Promise => { - const input: UpdateItemInput = { - Key: { - PK: { - S: primaryKey, - }, - }, - ExpressionAttributeValues: { - ":newState": { - S: state, - }, - ":newUpdateAt": { - S: new Date().toISOString(), - }, - }, - UpdateExpression: - "SET descriptorState = :newState, updatedAt = :newUpdateAt", - TableName: config.tokenGenerationReadModelTableNameTokenGeneration, - ReturnValues: "ALL_NEW", - }; - const command = new UpdateItemCommand(input); - await dynamoDBClient.send(command); -}; - export const updateDescriptorStateInPlatformStatesEntry = async ( dynamoDBClient: DynamoDBClient, primaryKey: PlatformStatesEServiceDescriptorPK, @@ -160,6 +131,7 @@ export const updateDescriptorStateInPlatformStatesEntry = async ( version: number ): Promise => { const input: UpdateItemInput = { + ConditionExpression: "attribute_exists(PK)", Key: { PK: { S: primaryKey, @@ -193,6 +165,7 @@ export const writeTokenStateEntry = async ( dynamoDBClient: DynamoDBClient ): Promise => { const input: PutItemInput = { + ConditionExpression: "attribute_not_exists(PK)", Item: { PK: { S: tokenStateEntry.PK, @@ -252,6 +225,7 @@ export const writeTokenStateEntry = async ( await dynamoDBClient.send(command); }; +/* export const readTokenStateEntryByEServiceIdAndDescriptorId = async ( eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, dynamoDBClient: DynamoDBClient @@ -259,13 +233,10 @@ export const readTokenStateEntryByEServiceIdAndDescriptorId = async ( const input: QueryInput = { TableName: config.tokenGenerationReadModelTableNameTokenGeneration, IndexName: "gsiIndex", // Use the name of your Global Secondary Index - KeyConditionExpression: `GSIPK_eserviceId_descriptorId = :gsi_value`, + KeyConditionExpression: `GSIPK_eserviceId_descriptorId = :gsiValue`, ExpressionAttributeValues: { - ":gsi_value": { S: eserviceId_descriptorId }, + ":gsiValue": { S: eserviceId_descriptorId }, }, - // ExpressionAttributeNames: { - // "#gsi": "GSIPK_eserviceId_descriptorId", - // }, ScanIndexForward: false, }; const command = new QueryCommand(input); @@ -288,11 +259,12 @@ export const readTokenStateEntryByEServiceIdAndDescriptorId = async ( return tokenStateEntry.data; } }; +*/ export const readTokenStateEntriesByEserviceIdAndDescriptorId = async ( eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, dynamoDBClient: DynamoDBClient -): Promise => { +): Promise => { const input: QueryInput = { TableName: config.tokenGenerationReadModelTableNameTokenGeneration, IndexName: "gsiIndex", // Use the name of your Global Secondary Index @@ -310,7 +282,9 @@ export const readTokenStateEntriesByEserviceIdAndDescriptorId = async ( // console.log("data.Items ", data); if (!data.Items) { - return undefined; + throw genericInternalError( + `Unable to read token state entries: result ${JSON.stringify(data)} ` + ); } else { const unmarshalledItems = data.Items.map((item) => unmarshall(item)); @@ -348,17 +322,28 @@ export const updateDescriptorStateInTokenGenerationStatesTable = async ( dynamoDBClient ); - if (entriesToUpdate) { - for (const entry of entriesToUpdate) { - await updateDescriptorState( - dynamoDBClient, - entry.PK, - descriptorStateToClientState(descriptorState) - ); - } - } else { - throw genericInternalError( - `Unable to find token generation state entry with GSIPK_eserviceId_descriptorId ${eserviceId_descriptorId}` - ); + for (const entry of entriesToUpdate) { + const input: UpdateItemInput = { + ConditionExpression: "attribute_exists(PK)", + Key: { + PK: { + S: entry.PK, + }, + }, + ExpressionAttributeValues: { + ":newState": { + S: descriptorStateToClientState(descriptorState), + }, + ":newUpdateAt": { + S: new Date().toISOString(), + }, + }, + UpdateExpression: + "SET descriptorState = :newState, updatedAt = :newUpdateAt", + TableName: config.tokenGenerationReadModelTableNameTokenGeneration, + ReturnValues: "ALL_NEW", + }; + const command = new UpdateItemCommand(input); + await dynamoDBClient.send(command); } }; From ba07d998600d85c34632935f5ca7618144c1c388 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Tue, 3 Sep 2024 15:55:16 +0200 Subject: [PATCH 090/241] Add todos --- packages/catalog-platformstate-writer/src/consumerServiceV2.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index be93ec5e30..32e7d8c32e 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -130,6 +130,7 @@ export async function handleMessageV2( { type: "EServiceDescriptorActivated" }, { type: "EServiceDescriptorSuspended" }, async (msg) => { + // TODO: add version check const { eservice, descriptor } = parseEServiceAndDescriptor( msg.data.eservice, unsafeBrandId(msg.data.descriptorId) @@ -171,6 +172,7 @@ export async function handleMessageV2( } ) .with({ type: "EServiceDescriptorArchived" }, async (msg) => { + // TODO: add version check const eserviceV2 = msg.data.eservice; if (!eserviceV2) { throw genericInternalError( From 3e72ad49372e118573524082ea681b9207de0c62 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Tue, 3 Sep 2024 15:56:21 +0200 Subject: [PATCH 091/241] Add todos --- packages/catalog-platformstate-writer/src/consumerServiceV1.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index 30b4b9ea21..e2d2458825 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -106,6 +106,7 @@ export async function handleMessageV1( } }) .with(descriptorState.suspended, async () => { + // TODO: add version check // platform-states await updateDescriptorStateInPlatformStatesEntry( dynamoDBClient, @@ -126,6 +127,7 @@ export async function handleMessageV1( ); }) .with(descriptorState.archived, async () => { + // TODO: add version check const eserviceId = unsafeBrandId(msg.data.eserviceId); const descriptorV1 = msg.data.eserviceDescriptor; if (!descriptorV1) { From 38df8e8e591a449d9734967f783c88cf7cb1daa6 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Wed, 4 Sep 2024 10:06:46 +0200 Subject: [PATCH 092/241] Add utils tests logics --- .../catalog-platformstate-writer/src/utils.ts | 2 +- ...logPlatformstateWriter.integration.test.ts | 214 ++++++++++++++++-- 2 files changed, 200 insertions(+), 16 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index 73627a40f6..cfb87197da 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -324,7 +324,7 @@ export const updateDescriptorStateInTokenGenerationStatesTable = async ( for (const entry of entriesToUpdate) { const input: UpdateItemInput = { - ConditionExpression: "attribute_exists(PK)", + ConditionExpression: "attribute_exists(GSIPK_eserviceId_descriptorId)", Key: { PK: { S: entry.PK, diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index b51dc7f450..a39a878bf8 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -45,11 +45,13 @@ import { getMockTokenStatesClientPurposeEntry, } from "pagopa-interop-commons-test"; import { + deleteCatalogEntry, descriptorStateToClientState, readCatalogEntry, readTokenStateEntriesByEserviceIdAndDescriptorId, sleep, updateDescriptorStateInPlatformStatesEntry, + updateDescriptorStateInTokenGenerationStatesTable, writeCatalogEntry, writeTokenStateEntry, } from "../src/utils.js"; @@ -178,41 +180,108 @@ describe("integration tests", async () => { eserviceId: generateId(), descriptorId: generateId(), }); - const previousStateEntry: PlatformStatesCatalogEntry = { + const catalogEntry: PlatformStatesCatalogEntry = { PK: primaryKey, state: itemState.inactive, descriptorAudience: "pagopa.it", version: 1, updatedAt: new Date().toISOString(), }; - await writeCatalogEntry(previousStateEntry, dynamoDBClient); + await writeCatalogEntry(catalogEntry, dynamoDBClient); expect( - writeCatalogEntry(previousStateEntry, dynamoDBClient) + writeCatalogEntry(catalogEntry, dynamoDBClient) ).rejects.toThrowError(ConditionalCheckFailedException); }); it("should write if previous entry doesn't exist", async () => { - expect(1).toBe(1); + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const catalogStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.inactive, + descriptorAudience: "pagopa.it", + version: 1, + updatedAt: new Date().toISOString(), + }; + // TODO: useful? + expect( + await readCatalogEntry(primaryKey, dynamoDBClient) + ).toBeUndefined(); + await writeCatalogEntry(catalogStateEntry, dynamoDBClient); + const expectedCatalogEntry = await readCatalogEntry( + primaryKey, + dynamoDBClient + ); + + expect(expectedCatalogEntry).toEqual(catalogStateEntry); }); }); describe("readCatalogEntry", async () => { it("should return undefined if entry doesn't exist", async () => { - expect(1).toBe(1); + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const catalogEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + expect(catalogEntry).toBeUndefined(); }); it("should return entry if it exists", async () => { - expect(1).toBe(1); + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const previousCatalogStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.inactive, + descriptorAudience: "pagopa.it", + version: 1, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousCatalogStateEntry, dynamoDBClient); + const expectedCatalogEntry = await readCatalogEntry( + primaryKey, + dynamoDBClient + ); + + expect(expectedCatalogEntry).toEqual(previousCatalogStateEntry); }); }); describe("deleteCatalogEntry", async () => { - it("should not throw error if previous entry doesn't exist", async () => { - expect(1).toBe(1); + // TODO: change with does NOT throw error + it.skip("should not throw error if previous entry doesn't exist", async () => { + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + expect( + deleteCatalogEntry(primaryKey, dynamoDBClient) + ).rejects.toThrowError(ConditionalCheckFailedException); }); it("should delete the entry if it exists", async () => { - expect(1).toBe(1); + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const previousCatalogStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.inactive, + descriptorAudience: "pagopa.it", + version: 1, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousCatalogStateEntry, dynamoDBClient); + await deleteCatalogEntry(primaryKey, dynamoDBClient); + const expectedCatalogEntry = await readCatalogEntry( + primaryKey, + dynamoDBClient + ); + expect(expectedCatalogEntry).toBeUndefined(); }); }); @@ -236,16 +305,60 @@ describe("integration tests", async () => { // token-generation-states describe("writeTokenStateEntry", async () => { it("should throw error if previous entry exists", async () => { - expect(1).toBe(1); + const tokenStateEntryPK = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const tokenStateEntry: TokenGenerationStatesClientPurposeEntry = { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK), + descriptorState: itemState.inactive, + descriptorAudience: "pagopa.it", + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(tokenStateEntry, dynamoDBClient); + expect( + writeTokenStateEntry(tokenStateEntry, dynamoDBClient) + ).rejects.toThrowError(ConditionalCheckFailedException); }); it("should write if previous entry doesn't exist", async () => { - expect(1).toBe(1); + const tokenStateEntryPK = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + // TODO: is this expect needed? + const previousTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + expect(previousTokenStateEntries).toEqual([]); + const tokenStateEntry: TokenGenerationStatesClientPurposeEntry = { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK), + descriptorState: itemState.inactive, + descriptorAudience: "pagopa.it", + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(tokenStateEntry, dynamoDBClient); + const expectedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + + expect(expectedTokenStateEntries).toEqual([tokenStateEntry]); }); }); describe("readTokenStateEntriesByEserviceIdAndDescriptorId", async () => { - // TODO: undefined? it("should return empty string if entries do not exist", async () => { const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: generateId(), @@ -300,12 +413,83 @@ describe("integration tests", async () => { }); describe("updateDescriptorStateInTokenGenerationStatesTable", async () => { - it("should throw error if previous entry doesn't exist", async () => { - expect(1).toBe(1); + // TODO: old description: should throw error if previous entry doesn't exist. Change with does NOT throw error + // FIXME: does nothing. Doesn't throw error + it.skip("should do nothing if previous entry doesn't exist", async () => { + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + expect( + updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptorState.archived, + dynamoDBClient + ) + ).rejects.toThrowError(ConditionalCheckFailedException); }); + // TODO: entry or entries? it("should update state if previous entry exists", async () => { - expect(1).toBe(1); + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: "pagopa.it", + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: "pagopa.it", + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptorState.published, + dynamoDBClient + ); + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: itemState.active, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: itemState.active, + updatedAt: new Date().toISOString(), + }; + + expect(retrievedTokenStateEntries).toHaveLength(2); + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry2, + expectedTokenStateEntry1, + ]) + ); }); }); }); From 011b351e2c45683d0bdd642eb424574a5922af07 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Wed, 4 Sep 2024 11:22:26 +0200 Subject: [PATCH 093/241] Fix comments --- packages/catalog-platformstate-writer/src/consumerServiceV2.ts | 3 +-- .../test/catalogPlatformstateWriter.integration.test.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index 32e7d8c32e..c1eec8eae3 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -52,6 +52,7 @@ export async function handleMessageV2( existingCatalogEntryCurrent.version > msg.version ) { // Stops processing if the message is older than the catalog entry + // TODO: return or return promise return; } else if ( existingCatalogEntryCurrent && @@ -130,7 +131,6 @@ export async function handleMessageV2( { type: "EServiceDescriptorActivated" }, { type: "EServiceDescriptorSuspended" }, async (msg) => { - // TODO: add version check const { eservice, descriptor } = parseEServiceAndDescriptor( msg.data.eservice, unsafeBrandId(msg.data.descriptorId) @@ -172,7 +172,6 @@ export async function handleMessageV2( } ) .with({ type: "EServiceDescriptorArchived" }, async (msg) => { - // TODO: add version check const eserviceV2 = msg.data.eservice; if (!eserviceV2) { throw genericInternalError( diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index a39a878bf8..8b34b4ff60 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -1007,7 +1007,7 @@ describe("integration tests", async () => { }); describe("EServiceDescriptorPublished (the previous descriptor becomes archived)", () => { - // these tests start with the basic flow for the current descriptor (simple write operation). Then, additinal checks are added + // these tests start with the basic flow for the current descriptor (simple write operation). Then, additional checks are added it("entry has to be deleted", async () => { const archivedDescriptor: Descriptor = { ...getMockDescriptor(), From 8ccda5b8171321603d7b233bf48137630deab5c5 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Wed, 4 Sep 2024 12:00:12 +0200 Subject: [PATCH 094/241] Fix utils tests + change gsi index name --- .../catalog-platformstate-writer/src/utils.ts | 4 +- ...logPlatformstateWriter.integration.test.ts | 99 ++++++++++++++++--- 2 files changed, 87 insertions(+), 16 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index cfb87197da..faf2c8d1eb 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -232,7 +232,7 @@ export const readTokenStateEntryByEServiceIdAndDescriptorId = async ( ): Promise => { const input: QueryInput = { TableName: config.tokenGenerationReadModelTableNameTokenGeneration, - IndexName: "gsiIndex", // Use the name of your Global Secondary Index + IndexName: "GSIPK_eserviceId_descriptorId", // Use the name of your Global Secondary Index KeyConditionExpression: `GSIPK_eserviceId_descriptorId = :gsiValue`, ExpressionAttributeValues: { ":gsiValue": { S: eserviceId_descriptorId }, @@ -267,7 +267,7 @@ export const readTokenStateEntriesByEserviceIdAndDescriptorId = async ( ): Promise => { const input: QueryInput = { TableName: config.tokenGenerationReadModelTableNameTokenGeneration, - IndexName: "gsiIndex", // Use the name of your Global Secondary Index + IndexName: "GSIPK_eserviceId_descriptorId", // Use the name of your Global Secondary Index KeyConditionExpression: `GSIPK_eserviceId_descriptorId = :gsi_value`, ExpressionAttributeValues: { ":gsi_value": { S: eserviceId_descriptorId }, diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 8b34b4ff60..901c0b7d1e 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -24,6 +24,7 @@ import { TokenGenerationStatesClientPurposeEntry, descriptorState, generateId, + genericInternalError, itemState, makeGSIPKEServiceIdDescriptorId, makePlatformStatesEServiceDescriptorPK, @@ -37,6 +38,9 @@ import { DeleteTableCommand, DeleteTableInput, DynamoDBClient, + ScanCommand, + ScanCommandOutput, + ScanInput, } from "@aws-sdk/client-dynamodb"; import { getMockDescriptor, @@ -44,6 +48,8 @@ import { getMockDocument, getMockTokenStatesClientPurposeEntry, } from "pagopa-interop-commons-test"; +import { unmarshall } from "@aws-sdk/util-dynamodb"; +import { z } from "zod"; import { deleteCatalogEntry, descriptorStateToClientState, @@ -98,7 +104,7 @@ describe("integration tests", async () => { GlobalSecondaryIndexes: [ { // TODO: change index name - IndexName: "gsiIndex", + IndexName: "GSIPK_eserviceId_descriptorId", KeySchema: [ { AttributeName: "GSIPK_eserviceId_descriptorId", @@ -170,7 +176,37 @@ describe("integration tests", async () => { }); it("should update state if previous entry exists", async () => { - expect(1).toBe(1); + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const previousCatalogStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.inactive, + descriptorAudience: "pagopa.it", + version: 1, + updatedAt: new Date().toISOString(), + }; + expect( + await readCatalogEntry(primaryKey, dynamoDBClient) + ).toBeUndefined(); + await writeCatalogEntry(previousCatalogStateEntry, dynamoDBClient); + await updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, + primaryKey, + itemState.active, + 2 + ); + + const result = await readCatalogEntry(primaryKey, dynamoDBClient); + const expectedCatalogEntry: PlatformStatesCatalogEntry = { + ...previousCatalogStateEntry, + state: itemState.active, + version: 2, + updatedAt: new Date().toISOString(), + }; + + expect(result).toEqual(expectedCatalogEntry); }); }); @@ -205,7 +241,6 @@ describe("integration tests", async () => { version: 1, updatedAt: new Date().toISOString(), }; - // TODO: useful? expect( await readCatalogEntry(primaryKey, dynamoDBClient) ).toBeUndefined(); @@ -252,15 +287,14 @@ describe("integration tests", async () => { }); describe("deleteCatalogEntry", async () => { - // TODO: change with does NOT throw error - it.skip("should not throw error if previous entry doesn't exist", async () => { + it("should not throw error if previous entry doesn't exist", async () => { const primaryKey = makePlatformStatesEServiceDescriptorPK({ eserviceId: generateId(), descriptorId: generateId(), }); expect( deleteCatalogEntry(primaryKey, dynamoDBClient) - ).rejects.toThrowError(ConditionalCheckFailedException); + ).resolves.not.toThrowError(); }); it("should delete the entry if it exists", async () => { @@ -334,7 +368,6 @@ describe("integration tests", async () => { eserviceId: generateId(), descriptorId: generateId(), }); - // TODO: is this expect needed? const previousTokenStateEntries = await readTokenStateEntriesByEserviceIdAndDescriptorId( eserviceId_descriptorId, @@ -359,7 +392,7 @@ describe("integration tests", async () => { }); describe("readTokenStateEntriesByEserviceIdAndDescriptorId", async () => { - it("should return empty string if entries do not exist", async () => { + it("should return empty array if entries do not exist", async () => { const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: generateId(), descriptorId: generateId(), @@ -413,24 +446,27 @@ describe("integration tests", async () => { }); describe("updateDescriptorStateInTokenGenerationStatesTable", async () => { - // TODO: old description: should throw error if previous entry doesn't exist. Change with does NOT throw error - // FIXME: does nothing. Doesn't throw error - it.skip("should do nothing if previous entry doesn't exist", async () => { + it("should do nothing if previous entry doesn't exist", async () => { const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: generateId(), descriptorId: generateId(), }); + const tokenStateEntries = await readAllTokenStateItems(dynamoDBClient); + expect(tokenStateEntries).toEqual([]); expect( updateDescriptorStateInTokenGenerationStatesTable( eserviceId_descriptorId, descriptorState.archived, dynamoDBClient ) - ).rejects.toThrowError(ConditionalCheckFailedException); + ).resolves.not.toThrowError(); + const tokenStateEntriesAfterUpdate = await readAllTokenStateItems( + dynamoDBClient + ); + expect(tokenStateEntriesAfterUpdate).toEqual([]); }); - // TODO: entry or entries? - it("should update state if previous entry exists", async () => { + it("should update state if previous entries exist", async () => { const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), kid: generateId(), @@ -1272,3 +1308,38 @@ describe("integration tests", async () => { }); }); }); + +const readAllTokenStateItems = async ( + dynamoDBClient: DynamoDBClient +): Promise => { + if (!config) { + fail(); + } + + const readInput: ScanInput = { + TableName: config.tokenGenerationReadModelTableNameTokenGeneration, + }; + const commandQuery = new ScanCommand(readInput); + const data: ScanCommandOutput = await dynamoDBClient.send(commandQuery); + + if (!data.Items) { + throw genericInternalError( + `Unable to read token state entries: result ${JSON.stringify(data)} ` + ); + } else { + const unmarshalledItems = data.Items.map((item) => unmarshall(item)); + + const tokenStateEntries = z + .array(TokenGenerationStatesClientPurposeEntry) + .safeParse(unmarshalledItems); + + if (!tokenStateEntries.success) { + throw genericInternalError( + `Unable to parse token state entry item: result ${JSON.stringify( + tokenStateEntries + )} - data ${JSON.stringify(data)} ` + ); + } + return tokenStateEntries.data; + } +}; From 633ab8a443912e316b189585114252d61d101d17 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:07:33 +0200 Subject: [PATCH 095/241] WIP: utils tests --- .../src/consumerServiceV1.ts | 52 ++- ...logPlatformstateWriter.integration.test.ts | 304 ++++++++++++------ 2 files changed, 241 insertions(+), 115 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index e2d2458825..797fc67bea 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -57,8 +57,8 @@ export async function handleMessageV1( existingCatalogEntry && existingCatalogEntry.version > msg.version ) { - return Promise.resolve(); // Stops processing if the message is older than the catalog entry + return Promise.resolve(); } else if ( existingCatalogEntry && existingCatalogEntry.version <= msg.version @@ -107,27 +107,45 @@ export async function handleMessageV1( }) .with(descriptorState.suspended, async () => { // TODO: add version check - // platform-states - await updateDescriptorStateInPlatformStatesEntry( - dynamoDBClient, + const existingCatalogEntry = await readCatalogEntry( eserviceDescriptorPK, - descriptorStateToClientState(descriptor.state), - msg.version - ); - - // token-generation-states - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId, - descriptorId: descriptor.id, - }); - await updateDescriptorStateInTokenGenerationStatesTable( - eserviceId_descriptorId, - descriptor.state, dynamoDBClient ); + + if (!existingCatalogEntry) { + console.log("!existingCatalogEntry"); + throw new Error("EServiceDescriptor not found in catalog"); + // throw genericInternalError( + // `EServiceDescriptor not found in catalog for event ${msg.type}` + // ); + } else if ( + existingCatalogEntry && + existingCatalogEntry.version > msg.version + ) { + // Stops processing if the message is older than the catalog entry + return Promise.resolve(); + } else { + // platform-states + await updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, + eserviceDescriptorPK, + descriptorStateToClientState(descriptor.state), + msg.version + ); + + // token-generation-states + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId, + descriptorId: descriptor.id, + }); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptor.state, + dynamoDBClient + ); + } }) .with(descriptorState.archived, async () => { - // TODO: add version check const eserviceId = unsafeBrandId(msg.data.eserviceId); const descriptorV1 = msg.data.eserviceDescriptor; if (!descriptorV1) { diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 71fb47c7de..f163dfaae7 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -21,6 +21,7 @@ import { EServiceDescriptorSuspendedV2, EServiceDescriptorUpdatedV1, EServiceEventEnvelope, + InternalError, PlatformStatesCatalogEntry, TokenGenerationStatesClientPurposeEntry, descriptorState, @@ -805,117 +806,224 @@ describe("integration tests", async () => { ); }); - it("EServiceDescriptorUpdated (published -> suspended)", async () => { - const publishedDescriptor: Descriptor = { - ...getMockDescriptor(), - audience: ["pagopa.it"], - interface: getMockDocument(), - state: descriptorState.published, - publishedAt: new Date(), - }; - const eservice: EService = { - ...getMockEService(), - descriptors: [publishedDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const suspendedDescriptor: Descriptor = { - ...publishedDescriptor, - suspendedAt: new Date(), - state: descriptorState.suspended, - }; + describe("EServiceDescriptorUpdated (published -> suspended)", () => { + it("should perform the update if msg.version >= existing version", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.published, + publishedAt: new Date(), + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [publishedDescriptor], + }; + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - const payload: EServiceDescriptorUpdatedV1 = { - eserviceId: eservice.id, - eserviceDescriptor: toDescriptorV1(suspendedDescriptor), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: eservice.id, - version: 2, - type: "EServiceDescriptorUpdated", - event_version: 1, - data: payload, - log_date: new Date(), - }; - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: suspendedDescriptor.id, - }); - const previousStateEntry: PlatformStatesCatalogEntry = { - PK: primaryKey, - state: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], - version: 1, - updatedAt: new Date().toISOString(), - }; - await writeCatalogEntry(previousStateEntry, dynamoDBClient); + const suspendedDescriptor: Descriptor = { + ...publishedDescriptor, + suspendedAt: new Date(), + state: descriptorState.suspended, + }; - // token-generation-states - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), - kid: generateId(), - }); - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, - }); - const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), - descriptorState: itemState.active, + const payload: EServiceDescriptorUpdatedV1 = { + eserviceId: eservice.id, + eserviceDescriptor: toDescriptorV1(suspendedDescriptor), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 2, + type: "EServiceDescriptorUpdated", + event_version: 1, + data: payload, + log_date: new Date(), + }; + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: suspendedDescriptor.id, + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.active, descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + version: 1, + updatedAt: new Date().toISOString(), }; - await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + await writeCatalogEntry(previousStateEntry, dynamoDBClient); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), - kid: generateId(), + // token-generation-states + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + clientId: generateId(), + kid: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + await handleMessageV1(message, dynamoDBClient); + await sleep(1000, mockDate); + + const retrievedEntry = await readCatalogEntry( + primaryKey, + dynamoDBClient + ); + const expectedEntry: PlatformStatesCatalogEntry = { + ...previousStateEntry, + state: itemState.inactive, + version: 2, + }; + expect(retrievedEntry).toEqual(expectedEntry); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry1, + expectedTokenStateEntry2, + ]) + ); }); - const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), - descriptorState: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + + it("should do nothing if msg.version < existing version", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.published, + publishedAt: new Date(), }; - await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); - await handleMessageV1(message, dynamoDBClient); - await sleep(1000, mockDate); + const eservice: EService = { + ...getMockEService(), + descriptors: [publishedDescriptor], + }; + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); - const expectedEntry: PlatformStatesCatalogEntry = { - ...previousStateEntry, - state: itemState.inactive, - version: 2, - }; - expect(retrievedEntry).toEqual(expectedEntry); + const suspendedDescriptor: Descriptor = { + ...publishedDescriptor, + suspendedAt: new Date(), + state: descriptorState.suspended, + }; - // token-generation-states - const retrievedTokenStateEntries = - await readTokenStateEntriesByEserviceIdAndDescriptorId( - eserviceId_descriptorId, + const payload: EServiceDescriptorUpdatedV1 = { + eserviceId: eservice.id, + eserviceDescriptor: toDescriptorV1(suspendedDescriptor), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 2, + type: "EServiceDescriptorUpdated", + event_version: 1, + data: payload, + log_date: new Date(), + }; + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: suspendedDescriptor.id, + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + version: 3, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); + await handleMessageV1(message, dynamoDBClient); + await sleep(1000, mockDate); + + const retrievedEntry = await readCatalogEntry( + primaryKey, dynamoDBClient ); - const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...previousTokenStateEntry1, - descriptorState: itemState.inactive, - updatedAt: new Date().toISOString(), + expect(retrievedEntry).toEqual(previousStateEntry); + }); + + it.only("should throw error if previous entry doesn't exist", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.published, + publishedAt: new Date(), }; - const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...previousTokenStateEntry2, - descriptorState: itemState.inactive, - updatedAt: new Date().toISOString(), + const eservice: EService = { + ...getMockEService(), + descriptors: [publishedDescriptor], }; - expect(retrievedTokenStateEntries).toEqual( - expect.arrayContaining([ - expectedTokenStateEntry1, - expectedTokenStateEntry2, - ]) - ); + // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); + + const suspendedDescriptor: Descriptor = { + ...publishedDescriptor, + suspendedAt: new Date(), + state: descriptorState.suspended, + }; + + const payload: EServiceDescriptorUpdatedV1 = { + eserviceId: eservice.id, + eserviceDescriptor: toDescriptorV1(suspendedDescriptor), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 2, + type: "EServiceDescriptorUpdated", + event_version: 1, + data: payload, + log_date: new Date(), + }; + await expect(() => + handleMessageV1(message, dynamoDBClient) + ).rejects.toThrowError(Error); + + // try { + // const a = await handleMessageV1(message, dynamoDBClient); + // console.log("try"); + // fail("should throw error"); + // } catch (e) { + // console.log("catch"); + // expect(1).toBe(1); + // } + }); }); it("EServiceDescriptorUpdated (published -> archived)", async () => { From c1a16411aea8d64fd9f7fbd6b04617e0d8d68399 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 4 Sep 2024 16:07:20 +0200 Subject: [PATCH 096/241] Fix test utils --- packages/catalog-platformstate-writer/src/utils.ts | 9 --------- .../catalogPlatformstateWriter.integration.test.ts | 3 +-- packages/catalog-platformstate-writer/test/utils.ts | 10 +++++++++- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index faf2c8d1eb..a97ce6b5f2 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -1,5 +1,4 @@ /* eslint-disable no-console */ -import { vi } from "vitest"; import { descriptorState, DescriptorState, @@ -303,14 +302,6 @@ export const readTokenStateEntriesByEserviceIdAndDescriptorId = async ( } }; -export const sleep = (ms: number, mockDate = new Date()): Promise => - new Promise((resolve) => { - vi.useRealTimers(); - setTimeout(resolve, ms); - vi.useFakeTimers(); - vi.setSystemTime(mockDate); - }); - export const updateDescriptorStateInTokenGenerationStatesTable = async ( eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, descriptorState: DescriptorState, diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 901c0b7d1e..518c9b128a 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -55,14 +55,13 @@ import { descriptorStateToClientState, readCatalogEntry, readTokenStateEntriesByEserviceIdAndDescriptorId, - sleep, updateDescriptorStateInPlatformStatesEntry, updateDescriptorStateInTokenGenerationStatesTable, writeCatalogEntry, writeTokenStateEntry, } from "../src/utils.js"; import { handleMessageV2 } from "../src/consumerServiceV2.js"; -import { config } from "./utils.js"; +import { config, sleep } from "./utils.js"; describe("integration tests", async () => { if (!config) { diff --git a/packages/catalog-platformstate-writer/test/utils.ts b/packages/catalog-platformstate-writer/test/utils.ts index 1931401502..15acf0455b 100644 --- a/packages/catalog-platformstate-writer/test/utils.ts +++ b/packages/catalog-platformstate-writer/test/utils.ts @@ -1,9 +1,17 @@ import { setupTestContainersVitest } from "pagopa-interop-commons-test/index.js"; -import { afterEach, inject } from "vitest"; +import { afterEach, inject, vi } from "vitest"; export const config = inject("tokenGenerationReadModelConfig"); export const { cleanup } = setupTestContainersVitest(); afterEach(cleanup); +export const sleep = (ms: number, mockDate = new Date()): Promise => + new Promise((resolve) => { + vi.useRealTimers(); + setTimeout(resolve, ms); + vi.useFakeTimers(); + vi.setSystemTime(mockDate); + }); + // export const eservices = readModelRepository.eservices; From 38c488fbfbc142b97506b180e1c1337fcab7cb14 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Wed, 4 Sep 2024 18:56:16 +0200 Subject: [PATCH 097/241] Fix EServiceDescriptorUpdated (suspended) test --- .../src/consumerServiceV1.ts | 219 +++++++++--------- ...logPlatformstateWriter.integration.test.ts | 17 +- 2 files changed, 110 insertions(+), 126 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index 797fc67bea..1d4b1a4c8f 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -41,126 +41,92 @@ export async function handleMessageV1( eserviceId, descriptorId: descriptor.id, }); - match(descriptor.state) - .with(descriptorState.published, async () => { - // steps: - // capire se siamo in (draft -> published) o (suspened -> published) - // fare query su platform states e vedere se c'è - // se non c'è sono in draft, e continuo questa esecuzione - // se c'è (presumibilmente come inactive) allora era suspended e sono nel caso sotto (sospensione e riattivazione hanno stesso handler) - const existingCatalogEntry = await readCatalogEntry( + if (descriptor.state === descriptorState.published) { + // steps: + // capire se siamo in (draft -> published) o (suspened -> published) + // fare query su platform states e vedere se c'è + // se non c'è sono in draft, e continuo questa esecuzione + // se c'è (presumibilmente come inactive) allora era suspended e sono nel caso sotto (sospensione e riattivazione hanno stesso handler) + const existingCatalogEntry = await readCatalogEntry( + eserviceDescriptorPK, + dynamoDBClient + ); + + if ( + existingCatalogEntry && + existingCatalogEntry.version > msg.version + ) { + // Stops processing if the message is older than the catalog entry + return Promise.resolve(); + } else if ( + existingCatalogEntry && + existingCatalogEntry.version <= msg.version + ) { + // suspended->published + await updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, eserviceDescriptorPK, - dynamoDBClient + descriptorStateToClientState(descriptor.state), + msg.version ); - if ( - existingCatalogEntry && - existingCatalogEntry.version > msg.version - ) { - // Stops processing if the message is older than the catalog entry - return Promise.resolve(); - } else if ( - existingCatalogEntry && - existingCatalogEntry.version <= msg.version - ) { - // suspended->published - - await updateDescriptorStateInPlatformStatesEntry( - dynamoDBClient, - eserviceDescriptorPK, - descriptorStateToClientState(descriptor.state), - msg.version - ); - - // token-generation-states - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId, - descriptorId: descriptor.id, - }); - await updateDescriptorStateInTokenGenerationStatesTable( - eserviceId_descriptorId, - descriptor.state, - dynamoDBClient - ); - } else { - const catalogEntry: PlatformStatesCatalogEntry = { - PK: eserviceDescriptorPK, - state: descriptorStateToClientState(descriptor.state), - descriptorAudience: descriptor.audience[0], - version: msg.version, - updatedAt: new Date().toISOString(), - }; - - await writeCatalogEntry(catalogEntry, dynamoDBClient); - - // token-generation-states - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId, - descriptorId: descriptor.id, - }); - await updateDescriptorStateInTokenGenerationStatesTable( - eserviceId_descriptorId, - descriptor.state, - dynamoDBClient - ); - } - }) - .with(descriptorState.suspended, async () => { - // TODO: add version check - const existingCatalogEntry = await readCatalogEntry( - eserviceDescriptorPK, + // token-generation-states + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId, + descriptorId: descriptor.id, + }); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptor.state, dynamoDBClient ); + } else { + const catalogEntry: PlatformStatesCatalogEntry = { + PK: eserviceDescriptorPK, + state: descriptorStateToClientState(descriptor.state), + descriptorAudience: descriptor.audience[0], + version: msg.version, + updatedAt: new Date().toISOString(), + }; - if (!existingCatalogEntry) { - console.log("!existingCatalogEntry"); - throw new Error("EServiceDescriptor not found in catalog"); - // throw genericInternalError( - // `EServiceDescriptor not found in catalog for event ${msg.type}` - // ); - } else if ( - existingCatalogEntry && - existingCatalogEntry.version > msg.version - ) { - // Stops processing if the message is older than the catalog entry - return Promise.resolve(); - } else { - // platform-states - await updateDescriptorStateInPlatformStatesEntry( - dynamoDBClient, - eserviceDescriptorPK, - descriptorStateToClientState(descriptor.state), - msg.version - ); + await writeCatalogEntry(catalogEntry, dynamoDBClient); - // token-generation-states - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId, - descriptorId: descriptor.id, - }); - await updateDescriptorStateInTokenGenerationStatesTable( - eserviceId_descriptorId, - descriptor.state, - dynamoDBClient - ); - } - }) - .with(descriptorState.archived, async () => { - const eserviceId = unsafeBrandId(msg.data.eserviceId); - const descriptorV1 = msg.data.eserviceDescriptor; - if (!descriptorV1) { - throw genericInternalError( - `EServiceDescriptor not found in message data for event ${msg.type}` - ); - } - const descriptor = fromDescriptorV1(descriptorV1); - - // platform-states - const primaryKey = makePlatformStatesEServiceDescriptorPK({ + // token-generation-states + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId, descriptorId: descriptor.id, }); - await deleteCatalogEntry(primaryKey, dynamoDBClient); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptor.state, + dynamoDBClient + ); + } + } else if (descriptor.state === descriptorState.suspended) { + // TODO: add version check + const existingCatalogEntry = await readCatalogEntry( + eserviceDescriptorPK, + dynamoDBClient + ); + + if (!existingCatalogEntry) { + throw genericInternalError( + `EServiceDescriptor not found in catalog for event ${msg.type}` + ); + } else if ( + existingCatalogEntry && + existingCatalogEntry.version > msg.version + ) { + // Stops processing if the message is older than the catalog entry + return Promise.resolve(); + } else { + // platform-states + await updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, + eserviceDescriptorPK, + descriptorStateToClientState(descriptor.state), + msg.version + ); // token-generation-states const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ @@ -172,10 +138,37 @@ export async function handleMessageV1( descriptor.state, dynamoDBClient ); - }) - .with(descriptorState.draft, descriptorState.deprecated, () => - Promise.resolve() + } + } else if (descriptor.state === descriptorState.archived) { + const eserviceId = unsafeBrandId(msg.data.eserviceId); + const descriptorV1 = msg.data.eserviceDescriptor; + if (!descriptorV1) { + throw genericInternalError( + `EServiceDescriptor not found in message data for event ${msg.type}` + ); + } + const descriptor = fromDescriptorV1(descriptorV1); + + // platform-states + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId, + descriptorId: descriptor.id, + }); + await deleteCatalogEntry(primaryKey, dynamoDBClient); + + // token-generation-states + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId, + descriptorId: descriptor.id, + }); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptor.state, + dynamoDBClient ); + } else { + return Promise.resolve(); + } }) .with( { type: "EServiceAdded" }, diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index f163dfaae7..25f31ec736 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -978,7 +978,7 @@ describe("integration tests", async () => { expect(retrievedEntry).toEqual(previousStateEntry); }); - it.only("should throw error if previous entry doesn't exist", async () => { + it("should throw error if previous entry doesn't exist", async () => { const publishedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], @@ -1011,18 +1011,9 @@ describe("integration tests", async () => { data: payload, log_date: new Date(), }; - await expect(() => - handleMessageV1(message, dynamoDBClient) - ).rejects.toThrowError(Error); - - // try { - // const a = await handleMessageV1(message, dynamoDBClient); - // console.log("try"); - // fail("should throw error"); - // } catch (e) { - // console.log("catch"); - // expect(1).toBe(1); - // } + expect(handleMessageV1(message, dynamoDBClient)).rejects.toThrowError( + InternalError + ); }); }); From 18c0569e1d21fa32a24cbad39315935682f1edc8 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Wed, 4 Sep 2024 23:41:48 +0200 Subject: [PATCH 098/241] Replace if/else with switch/case --- .../src/consumerServiceV1.ts | 216 +++++++++--------- 1 file changed, 112 insertions(+), 104 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index 1d4b1a4c8f..387cabedf6 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -41,92 +41,125 @@ export async function handleMessageV1( eserviceId, descriptorId: descriptor.id, }); - if (descriptor.state === descriptorState.published) { - // steps: - // capire se siamo in (draft -> published) o (suspened -> published) - // fare query su platform states e vedere se c'è - // se non c'è sono in draft, e continuo questa esecuzione - // se c'è (presumibilmente come inactive) allora era suspended e sono nel caso sotto (sospensione e riattivazione hanno stesso handler) - const existingCatalogEntry = await readCatalogEntry( - eserviceDescriptorPK, - dynamoDBClient - ); - - if ( - existingCatalogEntry && - existingCatalogEntry.version > msg.version - ) { - // Stops processing if the message is older than the catalog entry - return Promise.resolve(); - } else if ( - existingCatalogEntry && - existingCatalogEntry.version <= msg.version - ) { - // suspended->published - await updateDescriptorStateInPlatformStatesEntry( - dynamoDBClient, + switch (descriptor.state) { + case descriptorState.published: { + // steps: + // capire se siamo in (draft -> published) o (suspened -> published) + // fare query su platform states e vedere se c'è + // se non c'è sono in draft, e continuo questa esecuzione + // se c'è (presumibilmente come inactive) allora era suspended e sono nel caso sotto (sospensione e riattivazione hanno stesso handler) + const existingCatalogEntry = await readCatalogEntry( eserviceDescriptorPK, - descriptorStateToClientState(descriptor.state), - msg.version - ); - - // token-generation-states - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId, - descriptorId: descriptor.id, - }); - await updateDescriptorStateInTokenGenerationStatesTable( - eserviceId_descriptorId, - descriptor.state, dynamoDBClient ); - } else { - const catalogEntry: PlatformStatesCatalogEntry = { - PK: eserviceDescriptorPK, - state: descriptorStateToClientState(descriptor.state), - descriptorAudience: descriptor.audience[0], - version: msg.version, - updatedAt: new Date().toISOString(), - }; - await writeCatalogEntry(catalogEntry, dynamoDBClient); + if ( + existingCatalogEntry && + existingCatalogEntry.version > msg.version + ) { + // Stops processing if the message is older than the catalog entry + return Promise.resolve(); + } else if ( + existingCatalogEntry && + existingCatalogEntry.version <= msg.version + ) { + // suspended->published + await updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, + eserviceDescriptorPK, + descriptorStateToClientState(descriptor.state), + msg.version + ); - // token-generation-states - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId, - descriptorId: descriptor.id, - }); - await updateDescriptorStateInTokenGenerationStatesTable( - eserviceId_descriptorId, - descriptor.state, + // token-generation-states + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId, + descriptorId: descriptor.id, + }); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptor.state, + dynamoDBClient + ); + } else { + const catalogEntry: PlatformStatesCatalogEntry = { + PK: eserviceDescriptorPK, + state: descriptorStateToClientState(descriptor.state), + descriptorAudience: descriptor.audience[0], + version: msg.version, + updatedAt: new Date().toISOString(), + }; + + await writeCatalogEntry(catalogEntry, dynamoDBClient); + + // token-generation-states + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId, + descriptorId: descriptor.id, + }); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptor.state, + dynamoDBClient + ); + } + break; + } + case descriptorState.suspended: { + // TODO: add version check + const existingCatalogEntry = await readCatalogEntry( + eserviceDescriptorPK, dynamoDBClient ); + + if (!existingCatalogEntry) { + throw genericInternalError( + `EServiceDescriptor not found in catalog for event ${msg.type}` + ); + } else if ( + existingCatalogEntry && + existingCatalogEntry.version > msg.version + ) { + // Stops processing if the message is older than the catalog entry + return Promise.resolve(); + } else { + // platform-states + await updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, + eserviceDescriptorPK, + descriptorStateToClientState(descriptor.state), + msg.version + ); + + // token-generation-states + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId, + descriptorId: descriptor.id, + }); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptor.state, + dynamoDBClient + ); + } + break; } - } else if (descriptor.state === descriptorState.suspended) { - // TODO: add version check - const existingCatalogEntry = await readCatalogEntry( - eserviceDescriptorPK, - dynamoDBClient - ); + case descriptorState.archived: { + const eserviceId = unsafeBrandId(msg.data.eserviceId); + const descriptorV1 = msg.data.eserviceDescriptor; + if (!descriptorV1) { + throw genericInternalError( + `EServiceDescriptor not found in message data for event ${msg.type}` + ); + } + const descriptor = fromDescriptorV1(descriptorV1); - if (!existingCatalogEntry) { - throw genericInternalError( - `EServiceDescriptor not found in catalog for event ${msg.type}` - ); - } else if ( - existingCatalogEntry && - existingCatalogEntry.version > msg.version - ) { - // Stops processing if the message is older than the catalog entry - return Promise.resolve(); - } else { // platform-states - await updateDescriptorStateInPlatformStatesEntry( - dynamoDBClient, - eserviceDescriptorPK, - descriptorStateToClientState(descriptor.state), - msg.version - ); + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId, + descriptorId: descriptor.id, + }); + await deleteCatalogEntry(primaryKey, dynamoDBClient); // token-generation-states const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ @@ -138,36 +171,11 @@ export async function handleMessageV1( descriptor.state, dynamoDBClient ); + break; } - } else if (descriptor.state === descriptorState.archived) { - const eserviceId = unsafeBrandId(msg.data.eserviceId); - const descriptorV1 = msg.data.eserviceDescriptor; - if (!descriptorV1) { - throw genericInternalError( - `EServiceDescriptor not found in message data for event ${msg.type}` - ); + default: { + return Promise.resolve(); } - const descriptor = fromDescriptorV1(descriptorV1); - - // platform-states - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId, - descriptorId: descriptor.id, - }); - await deleteCatalogEntry(primaryKey, dynamoDBClient); - - // token-generation-states - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId, - descriptorId: descriptor.id, - }); - await updateDescriptorStateInTokenGenerationStatesTable( - eserviceId_descriptorId, - descriptor.state, - dynamoDBClient - ); - } else { - return Promise.resolve(); } }) .with( From a4ec8a8621be8ae461b23b6c76b24f875d47ebde Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Thu, 5 Sep 2024 10:34:03 +0200 Subject: [PATCH 099/241] Revert "Replace if/else with switch/case" This reverts commit 18c0569e1d21fa32a24cbad39315935682f1edc8. --- .../src/consumerServiceV1.ts | 216 +++++++++--------- 1 file changed, 104 insertions(+), 112 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index 387cabedf6..1d4b1a4c8f 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -41,125 +41,92 @@ export async function handleMessageV1( eserviceId, descriptorId: descriptor.id, }); - switch (descriptor.state) { - case descriptorState.published: { - // steps: - // capire se siamo in (draft -> published) o (suspened -> published) - // fare query su platform states e vedere se c'è - // se non c'è sono in draft, e continuo questa esecuzione - // se c'è (presumibilmente come inactive) allora era suspended e sono nel caso sotto (sospensione e riattivazione hanno stesso handler) - const existingCatalogEntry = await readCatalogEntry( + if (descriptor.state === descriptorState.published) { + // steps: + // capire se siamo in (draft -> published) o (suspened -> published) + // fare query su platform states e vedere se c'è + // se non c'è sono in draft, e continuo questa esecuzione + // se c'è (presumibilmente come inactive) allora era suspended e sono nel caso sotto (sospensione e riattivazione hanno stesso handler) + const existingCatalogEntry = await readCatalogEntry( + eserviceDescriptorPK, + dynamoDBClient + ); + + if ( + existingCatalogEntry && + existingCatalogEntry.version > msg.version + ) { + // Stops processing if the message is older than the catalog entry + return Promise.resolve(); + } else if ( + existingCatalogEntry && + existingCatalogEntry.version <= msg.version + ) { + // suspended->published + await updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, eserviceDescriptorPK, - dynamoDBClient + descriptorStateToClientState(descriptor.state), + msg.version ); - if ( - existingCatalogEntry && - existingCatalogEntry.version > msg.version - ) { - // Stops processing if the message is older than the catalog entry - return Promise.resolve(); - } else if ( - existingCatalogEntry && - existingCatalogEntry.version <= msg.version - ) { - // suspended->published - await updateDescriptorStateInPlatformStatesEntry( - dynamoDBClient, - eserviceDescriptorPK, - descriptorStateToClientState(descriptor.state), - msg.version - ); - - // token-generation-states - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId, - descriptorId: descriptor.id, - }); - await updateDescriptorStateInTokenGenerationStatesTable( - eserviceId_descriptorId, - descriptor.state, - dynamoDBClient - ); - } else { - const catalogEntry: PlatformStatesCatalogEntry = { - PK: eserviceDescriptorPK, - state: descriptorStateToClientState(descriptor.state), - descriptorAudience: descriptor.audience[0], - version: msg.version, - updatedAt: new Date().toISOString(), - }; - - await writeCatalogEntry(catalogEntry, dynamoDBClient); - - // token-generation-states - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId, - descriptorId: descriptor.id, - }); - await updateDescriptorStateInTokenGenerationStatesTable( - eserviceId_descriptorId, - descriptor.state, - dynamoDBClient - ); - } - break; - } - case descriptorState.suspended: { - // TODO: add version check - const existingCatalogEntry = await readCatalogEntry( - eserviceDescriptorPK, + // token-generation-states + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId, + descriptorId: descriptor.id, + }); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptor.state, dynamoDBClient ); + } else { + const catalogEntry: PlatformStatesCatalogEntry = { + PK: eserviceDescriptorPK, + state: descriptorStateToClientState(descriptor.state), + descriptorAudience: descriptor.audience[0], + version: msg.version, + updatedAt: new Date().toISOString(), + }; - if (!existingCatalogEntry) { - throw genericInternalError( - `EServiceDescriptor not found in catalog for event ${msg.type}` - ); - } else if ( - existingCatalogEntry && - existingCatalogEntry.version > msg.version - ) { - // Stops processing if the message is older than the catalog entry - return Promise.resolve(); - } else { - // platform-states - await updateDescriptorStateInPlatformStatesEntry( - dynamoDBClient, - eserviceDescriptorPK, - descriptorStateToClientState(descriptor.state), - msg.version - ); + await writeCatalogEntry(catalogEntry, dynamoDBClient); - // token-generation-states - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId, - descriptorId: descriptor.id, - }); - await updateDescriptorStateInTokenGenerationStatesTable( - eserviceId_descriptorId, - descriptor.state, - dynamoDBClient - ); - } - break; - } - case descriptorState.archived: { - const eserviceId = unsafeBrandId(msg.data.eserviceId); - const descriptorV1 = msg.data.eserviceDescriptor; - if (!descriptorV1) { - throw genericInternalError( - `EServiceDescriptor not found in message data for event ${msg.type}` - ); - } - const descriptor = fromDescriptorV1(descriptorV1); - - // platform-states - const primaryKey = makePlatformStatesEServiceDescriptorPK({ + // token-generation-states + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId, descriptorId: descriptor.id, }); - await deleteCatalogEntry(primaryKey, dynamoDBClient); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptor.state, + dynamoDBClient + ); + } + } else if (descriptor.state === descriptorState.suspended) { + // TODO: add version check + const existingCatalogEntry = await readCatalogEntry( + eserviceDescriptorPK, + dynamoDBClient + ); + + if (!existingCatalogEntry) { + throw genericInternalError( + `EServiceDescriptor not found in catalog for event ${msg.type}` + ); + } else if ( + existingCatalogEntry && + existingCatalogEntry.version > msg.version + ) { + // Stops processing if the message is older than the catalog entry + return Promise.resolve(); + } else { + // platform-states + await updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, + eserviceDescriptorPK, + descriptorStateToClientState(descriptor.state), + msg.version + ); // token-generation-states const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ @@ -171,11 +138,36 @@ export async function handleMessageV1( descriptor.state, dynamoDBClient ); - break; } - default: { - return Promise.resolve(); + } else if (descriptor.state === descriptorState.archived) { + const eserviceId = unsafeBrandId(msg.data.eserviceId); + const descriptorV1 = msg.data.eserviceDescriptor; + if (!descriptorV1) { + throw genericInternalError( + `EServiceDescriptor not found in message data for event ${msg.type}` + ); } + const descriptor = fromDescriptorV1(descriptorV1); + + // platform-states + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId, + descriptorId: descriptor.id, + }); + await deleteCatalogEntry(primaryKey, dynamoDBClient); + + // token-generation-states + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId, + descriptorId: descriptor.id, + }); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptor.state, + dynamoDBClient + ); + } else { + return Promise.resolve(); } }) .with( From 67d4c1bb4c89cb9ef0c05933073f8b023d314659 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Thu, 5 Sep 2024 10:34:09 +0200 Subject: [PATCH 100/241] Revert "Merge branch 'IMN-798_catalog-platformstate-writer-v2' into IMN-797_catalog-platformstate-writer-v1" This reverts commit 1f10e9a685e7baae5c49384558dc59f4924f72b5, reversing changes made to 38c488fbfbc142b97506b180e1c1337fcab7cb14. --- packages/catalog-platformstate-writer/src/utils.ts | 9 +++++++++ .../catalogPlatformstateWriter.integration.test.ts | 3 ++- packages/catalog-platformstate-writer/test/utils.ts | 10 +--------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index a97ce6b5f2..faf2c8d1eb 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -1,4 +1,5 @@ /* eslint-disable no-console */ +import { vi } from "vitest"; import { descriptorState, DescriptorState, @@ -302,6 +303,14 @@ export const readTokenStateEntriesByEserviceIdAndDescriptorId = async ( } }; +export const sleep = (ms: number, mockDate = new Date()): Promise => + new Promise((resolve) => { + vi.useRealTimers(); + setTimeout(resolve, ms); + vi.useFakeTimers(); + vi.setSystemTime(mockDate); + }); + export const updateDescriptorStateInTokenGenerationStatesTable = async ( eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, descriptorState: DescriptorState, diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index ae63812028..25f31ec736 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -59,13 +59,14 @@ import { descriptorStateToClientState, readCatalogEntry, readTokenStateEntriesByEserviceIdAndDescriptorId, + sleep, updateDescriptorStateInPlatformStatesEntry, updateDescriptorStateInTokenGenerationStatesTable, writeCatalogEntry, writeTokenStateEntry, } from "../src/utils.js"; import { handleMessageV2 } from "../src/consumerServiceV2.js"; -import { config, sleep } from "./utils.js"; +import { config } from "./utils.js"; describe("integration tests", async () => { if (!config) { diff --git a/packages/catalog-platformstate-writer/test/utils.ts b/packages/catalog-platformstate-writer/test/utils.ts index 15acf0455b..1931401502 100644 --- a/packages/catalog-platformstate-writer/test/utils.ts +++ b/packages/catalog-platformstate-writer/test/utils.ts @@ -1,17 +1,9 @@ import { setupTestContainersVitest } from "pagopa-interop-commons-test/index.js"; -import { afterEach, inject, vi } from "vitest"; +import { afterEach, inject } from "vitest"; export const config = inject("tokenGenerationReadModelConfig"); export const { cleanup } = setupTestContainersVitest(); afterEach(cleanup); -export const sleep = (ms: number, mockDate = new Date()): Promise => - new Promise((resolve) => { - vi.useRealTimers(); - setTimeout(resolve, ms); - vi.useFakeTimers(); - vi.setSystemTime(mockDate); - }); - // export const eservices = readModelRepository.eservices; From 317b30d8108db16802e2cd23a2bff46129f7e76a Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Thu, 5 Sep 2024 10:34:12 +0200 Subject: [PATCH 101/241] Revert "Fix EServiceDescriptorUpdated (suspended) test" This reverts commit 38c488fbfbc142b97506b180e1c1337fcab7cb14. --- .../src/consumerServiceV1.ts | 219 +++++++++--------- ...logPlatformstateWriter.integration.test.ts | 17 +- 2 files changed, 126 insertions(+), 110 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index 1d4b1a4c8f..797fc67bea 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -41,92 +41,126 @@ export async function handleMessageV1( eserviceId, descriptorId: descriptor.id, }); - if (descriptor.state === descriptorState.published) { - // steps: - // capire se siamo in (draft -> published) o (suspened -> published) - // fare query su platform states e vedere se c'è - // se non c'è sono in draft, e continuo questa esecuzione - // se c'è (presumibilmente come inactive) allora era suspended e sono nel caso sotto (sospensione e riattivazione hanno stesso handler) - const existingCatalogEntry = await readCatalogEntry( - eserviceDescriptorPK, - dynamoDBClient - ); - - if ( - existingCatalogEntry && - existingCatalogEntry.version > msg.version - ) { - // Stops processing if the message is older than the catalog entry - return Promise.resolve(); - } else if ( - existingCatalogEntry && - existingCatalogEntry.version <= msg.version - ) { - // suspended->published - await updateDescriptorStateInPlatformStatesEntry( - dynamoDBClient, + match(descriptor.state) + .with(descriptorState.published, async () => { + // steps: + // capire se siamo in (draft -> published) o (suspened -> published) + // fare query su platform states e vedere se c'è + // se non c'è sono in draft, e continuo questa esecuzione + // se c'è (presumibilmente come inactive) allora era suspended e sono nel caso sotto (sospensione e riattivazione hanno stesso handler) + const existingCatalogEntry = await readCatalogEntry( eserviceDescriptorPK, - descriptorStateToClientState(descriptor.state), - msg.version + dynamoDBClient ); - // token-generation-states - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId, - descriptorId: descriptor.id, - }); - await updateDescriptorStateInTokenGenerationStatesTable( - eserviceId_descriptorId, - descriptor.state, + if ( + existingCatalogEntry && + existingCatalogEntry.version > msg.version + ) { + // Stops processing if the message is older than the catalog entry + return Promise.resolve(); + } else if ( + existingCatalogEntry && + existingCatalogEntry.version <= msg.version + ) { + // suspended->published + + await updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, + eserviceDescriptorPK, + descriptorStateToClientState(descriptor.state), + msg.version + ); + + // token-generation-states + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId, + descriptorId: descriptor.id, + }); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptor.state, + dynamoDBClient + ); + } else { + const catalogEntry: PlatformStatesCatalogEntry = { + PK: eserviceDescriptorPK, + state: descriptorStateToClientState(descriptor.state), + descriptorAudience: descriptor.audience[0], + version: msg.version, + updatedAt: new Date().toISOString(), + }; + + await writeCatalogEntry(catalogEntry, dynamoDBClient); + + // token-generation-states + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId, + descriptorId: descriptor.id, + }); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptor.state, + dynamoDBClient + ); + } + }) + .with(descriptorState.suspended, async () => { + // TODO: add version check + const existingCatalogEntry = await readCatalogEntry( + eserviceDescriptorPK, dynamoDBClient ); - } else { - const catalogEntry: PlatformStatesCatalogEntry = { - PK: eserviceDescriptorPK, - state: descriptorStateToClientState(descriptor.state), - descriptorAudience: descriptor.audience[0], - version: msg.version, - updatedAt: new Date().toISOString(), - }; - await writeCatalogEntry(catalogEntry, dynamoDBClient); + if (!existingCatalogEntry) { + console.log("!existingCatalogEntry"); + throw new Error("EServiceDescriptor not found in catalog"); + // throw genericInternalError( + // `EServiceDescriptor not found in catalog for event ${msg.type}` + // ); + } else if ( + existingCatalogEntry && + existingCatalogEntry.version > msg.version + ) { + // Stops processing if the message is older than the catalog entry + return Promise.resolve(); + } else { + // platform-states + await updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, + eserviceDescriptorPK, + descriptorStateToClientState(descriptor.state), + msg.version + ); - // token-generation-states - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + // token-generation-states + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId, + descriptorId: descriptor.id, + }); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptor.state, + dynamoDBClient + ); + } + }) + .with(descriptorState.archived, async () => { + const eserviceId = unsafeBrandId(msg.data.eserviceId); + const descriptorV1 = msg.data.eserviceDescriptor; + if (!descriptorV1) { + throw genericInternalError( + `EServiceDescriptor not found in message data for event ${msg.type}` + ); + } + const descriptor = fromDescriptorV1(descriptorV1); + + // platform-states + const primaryKey = makePlatformStatesEServiceDescriptorPK({ eserviceId, descriptorId: descriptor.id, }); - await updateDescriptorStateInTokenGenerationStatesTable( - eserviceId_descriptorId, - descriptor.state, - dynamoDBClient - ); - } - } else if (descriptor.state === descriptorState.suspended) { - // TODO: add version check - const existingCatalogEntry = await readCatalogEntry( - eserviceDescriptorPK, - dynamoDBClient - ); - - if (!existingCatalogEntry) { - throw genericInternalError( - `EServiceDescriptor not found in catalog for event ${msg.type}` - ); - } else if ( - existingCatalogEntry && - existingCatalogEntry.version > msg.version - ) { - // Stops processing if the message is older than the catalog entry - return Promise.resolve(); - } else { - // platform-states - await updateDescriptorStateInPlatformStatesEntry( - dynamoDBClient, - eserviceDescriptorPK, - descriptorStateToClientState(descriptor.state), - msg.version - ); + await deleteCatalogEntry(primaryKey, dynamoDBClient); // token-generation-states const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ @@ -138,37 +172,10 @@ export async function handleMessageV1( descriptor.state, dynamoDBClient ); - } - } else if (descriptor.state === descriptorState.archived) { - const eserviceId = unsafeBrandId(msg.data.eserviceId); - const descriptorV1 = msg.data.eserviceDescriptor; - if (!descriptorV1) { - throw genericInternalError( - `EServiceDescriptor not found in message data for event ${msg.type}` - ); - } - const descriptor = fromDescriptorV1(descriptorV1); - - // platform-states - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId, - descriptorId: descriptor.id, - }); - await deleteCatalogEntry(primaryKey, dynamoDBClient); - - // token-generation-states - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId, - descriptorId: descriptor.id, - }); - await updateDescriptorStateInTokenGenerationStatesTable( - eserviceId_descriptorId, - descriptor.state, - dynamoDBClient + }) + .with(descriptorState.draft, descriptorState.deprecated, () => + Promise.resolve() ); - } else { - return Promise.resolve(); - } }) .with( { type: "EServiceAdded" }, diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 25f31ec736..f163dfaae7 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -978,7 +978,7 @@ describe("integration tests", async () => { expect(retrievedEntry).toEqual(previousStateEntry); }); - it("should throw error if previous entry doesn't exist", async () => { + it.only("should throw error if previous entry doesn't exist", async () => { const publishedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], @@ -1011,9 +1011,18 @@ describe("integration tests", async () => { data: payload, log_date: new Date(), }; - expect(handleMessageV1(message, dynamoDBClient)).rejects.toThrowError( - InternalError - ); + await expect(() => + handleMessageV1(message, dynamoDBClient) + ).rejects.toThrowError(Error); + + // try { + // const a = await handleMessageV1(message, dynamoDBClient); + // console.log("try"); + // fail("should throw error"); + // } catch (e) { + // console.log("catch"); + // expect(1).toBe(1); + // } }); }); From b7ab65f6cc757dcc15ddbbbd642b0ba6c7427553 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Thu, 5 Sep 2024 10:37:12 +0200 Subject: [PATCH 102/241] Fix match --- .../catalog-platformstate-writer/src/consumerServiceV1.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index 797fc67bea..a9fefc1051 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -41,7 +41,7 @@ export async function handleMessageV1( eserviceId, descriptorId: descriptor.id, }); - match(descriptor.state) + await match(descriptor.state) .with(descriptorState.published, async () => { // steps: // capire se siamo in (draft -> published) o (suspened -> published) @@ -175,7 +175,8 @@ export async function handleMessageV1( }) .with(descriptorState.draft, descriptorState.deprecated, () => Promise.resolve() - ); + ) + .exhaustive(); }) .with( { type: "EServiceAdded" }, From 7006aeb24b59aaf4a5475500be6a810fbe2d550a Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Thu, 5 Sep 2024 10:41:10 +0200 Subject: [PATCH 103/241] Clean code --- .../src/consumerServiceV1.ts | 8 +++----- ...atalogPlatformstateWriter.integration.test.ts | 16 ++-------------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index a9fefc1051..01982fb2f0 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -113,11 +113,9 @@ export async function handleMessageV1( ); if (!existingCatalogEntry) { - console.log("!existingCatalogEntry"); - throw new Error("EServiceDescriptor not found in catalog"); - // throw genericInternalError( - // `EServiceDescriptor not found in catalog for event ${msg.type}` - // ); + throw genericInternalError( + `EServiceDescriptor not found in catalog for event ${msg.type}` + ); } else if ( existingCatalogEntry && existingCatalogEntry.version > msg.version diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index f163dfaae7..678669b594 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -21,7 +21,6 @@ import { EServiceDescriptorSuspendedV2, EServiceDescriptorUpdatedV1, EServiceEventEnvelope, - InternalError, PlatformStatesCatalogEntry, TokenGenerationStatesClientPurposeEntry, descriptorState, @@ -978,7 +977,7 @@ describe("integration tests", async () => { expect(retrievedEntry).toEqual(previousStateEntry); }); - it.only("should throw error if previous entry doesn't exist", async () => { + it("should throw error if previous entry doesn't exist", async () => { const publishedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], @@ -1011,18 +1010,7 @@ describe("integration tests", async () => { data: payload, log_date: new Date(), }; - await expect(() => - handleMessageV1(message, dynamoDBClient) - ).rejects.toThrowError(Error); - - // try { - // const a = await handleMessageV1(message, dynamoDBClient); - // console.log("try"); - // fail("should throw error"); - // } catch (e) { - // console.log("catch"); - // expect(1).toBe(1); - // } + expect(handleMessageV1(message, dynamoDBClient)).rejects.toThrowError(); }); }); From 24a84e732ae6cada3ceffa8e7b717cfdfbd28072 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Thu, 5 Sep 2024 10:51:06 +0200 Subject: [PATCH 104/241] Reapply "Merge branch 'IMN-798_catalog-platformstate-writer-v2' into IMN-797_catalog-platformstate-writer-v1" This reverts commit 67d4c1bb4c89cb9ef0c05933073f8b023d314659. --- packages/catalog-platformstate-writer/src/utils.ts | 9 --------- .../catalogPlatformstateWriter.integration.test.ts | 3 +-- packages/catalog-platformstate-writer/test/utils.ts | 10 +++++++++- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index faf2c8d1eb..a97ce6b5f2 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -1,5 +1,4 @@ /* eslint-disable no-console */ -import { vi } from "vitest"; import { descriptorState, DescriptorState, @@ -303,14 +302,6 @@ export const readTokenStateEntriesByEserviceIdAndDescriptorId = async ( } }; -export const sleep = (ms: number, mockDate = new Date()): Promise => - new Promise((resolve) => { - vi.useRealTimers(); - setTimeout(resolve, ms); - vi.useFakeTimers(); - vi.setSystemTime(mockDate); - }); - export const updateDescriptorStateInTokenGenerationStatesTable = async ( eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, descriptorState: DescriptorState, diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 678669b594..b1ca27dc44 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -58,14 +58,13 @@ import { descriptorStateToClientState, readCatalogEntry, readTokenStateEntriesByEserviceIdAndDescriptorId, - sleep, updateDescriptorStateInPlatformStatesEntry, updateDescriptorStateInTokenGenerationStatesTable, writeCatalogEntry, writeTokenStateEntry, } from "../src/utils.js"; import { handleMessageV2 } from "../src/consumerServiceV2.js"; -import { config } from "./utils.js"; +import { config, sleep } from "./utils.js"; describe("integration tests", async () => { if (!config) { diff --git a/packages/catalog-platformstate-writer/test/utils.ts b/packages/catalog-platformstate-writer/test/utils.ts index 1931401502..15acf0455b 100644 --- a/packages/catalog-platformstate-writer/test/utils.ts +++ b/packages/catalog-platformstate-writer/test/utils.ts @@ -1,9 +1,17 @@ import { setupTestContainersVitest } from "pagopa-interop-commons-test/index.js"; -import { afterEach, inject } from "vitest"; +import { afterEach, inject, vi } from "vitest"; export const config = inject("tokenGenerationReadModelConfig"); export const { cleanup } = setupTestContainersVitest(); afterEach(cleanup); +export const sleep = (ms: number, mockDate = new Date()): Promise => + new Promise((resolve) => { + vi.useRealTimers(); + setTimeout(resolve, ms); + vi.useFakeTimers(); + vi.setSystemTime(mockDate); + }); + // export const eservices = readModelRepository.eservices; From fcd43a3f8eff410fa3834e7b752da795aab289bd Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 5 Sep 2024 11:23:34 +0200 Subject: [PATCH 105/241] Fix sleep --- packages/catalog-platformstate-writer/src/utils.ts | 9 --------- .../catalogPlatformstateWriter.integration.test.ts | 3 +-- packages/catalog-platformstate-writer/test/utils.ts | 10 +++++++++- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index faf2c8d1eb..a97ce6b5f2 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -1,5 +1,4 @@ /* eslint-disable no-console */ -import { vi } from "vitest"; import { descriptorState, DescriptorState, @@ -303,14 +302,6 @@ export const readTokenStateEntriesByEserviceIdAndDescriptorId = async ( } }; -export const sleep = (ms: number, mockDate = new Date()): Promise => - new Promise((resolve) => { - vi.useRealTimers(); - setTimeout(resolve, ms); - vi.useFakeTimers(); - vi.setSystemTime(mockDate); - }); - export const updateDescriptorStateInTokenGenerationStatesTable = async ( eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, descriptorState: DescriptorState, diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 678669b594..b1ca27dc44 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -58,14 +58,13 @@ import { descriptorStateToClientState, readCatalogEntry, readTokenStateEntriesByEserviceIdAndDescriptorId, - sleep, updateDescriptorStateInPlatformStatesEntry, updateDescriptorStateInTokenGenerationStatesTable, writeCatalogEntry, writeTokenStateEntry, } from "../src/utils.js"; import { handleMessageV2 } from "../src/consumerServiceV2.js"; -import { config } from "./utils.js"; +import { config, sleep } from "./utils.js"; describe("integration tests", async () => { if (!config) { diff --git a/packages/catalog-platformstate-writer/test/utils.ts b/packages/catalog-platformstate-writer/test/utils.ts index 1931401502..dc4ac6161b 100644 --- a/packages/catalog-platformstate-writer/test/utils.ts +++ b/packages/catalog-platformstate-writer/test/utils.ts @@ -1,5 +1,5 @@ import { setupTestContainersVitest } from "pagopa-interop-commons-test/index.js"; -import { afterEach, inject } from "vitest"; +import { afterEach, inject, vi } from "vitest"; export const config = inject("tokenGenerationReadModelConfig"); export const { cleanup } = setupTestContainersVitest(); @@ -7,3 +7,11 @@ export const { cleanup } = setupTestContainersVitest(); afterEach(cleanup); // export const eservices = readModelRepository.eservices; + +export const sleep = (ms: number, mockDate = new Date()): Promise => + new Promise((resolve) => { + vi.useRealTimers(); + setTimeout(resolve, ms); + vi.useFakeTimers(); + vi.setSystemTime(mockDate); + }); From fce8932bc80396a8193c0dfa0b5796c3d3ec6694 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 5 Sep 2024 11:23:57 +0200 Subject: [PATCH 106/241] Update docker compose --- docker/docker-compose.yml | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 9d02556f87..2f60dded1a 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -68,14 +68,7 @@ services: environment: AWS_ACCESS_KEY_ID: keyid AWS_SECRET_ACCESS_KEY: key - command: dynamodb create-table - --table-name platform-states - --attribute-definitions - AttributeName=PK,AttributeType=S - --key-schema - AttributeName=PK,KeyType=HASH - --billing-mode PAY_PER_REQUEST - --endpoint-url http://token-generation-readmodel:8000 --region eu-central-1 + command: dynamodb create-table --table-name platform-states --attribute-definitions AttributeName=PK,AttributeType=S --key-schema AttributeName=PK,KeyType=HASH --billing-mode PAY_PER_REQUEST --endpoint-url http://token-generation-readmodel:8000 --region eu-central-1 token-generation-states-table-init: depends_on: @@ -85,14 +78,19 @@ services: environment: AWS_ACCESS_KEY_ID: keyid AWS_SECRET_ACCESS_KEY: key - command: dynamodb create-table - --table-name token-generation-states - --attribute-definitions - AttributeName=PK,AttributeType=S - --key-schema - AttributeName=PK,KeyType=HASH - --billing-mode PAY_PER_REQUEST - --endpoint-url http://token-generation-readmodel:8000 --region eu-central-1 + command: dynamodb create-table --table-name token-generation-states --attribute-definitions AttributeName=PK,AttributeType=S AttributeName=GSIPK_eserviceId_descriptorId,AttributeType=S --key-schema AttributeName=PK,KeyType=HASH --global-secondary-indexes "IndexName=GSIPK_eserviceId_descriptorId,KeySchema=[{AttributeName=GSIPK_eserviceId_descriptorId,KeyType=HASH}],Projection={ProjectionType=ALL}" --billing-mode PAY_PER_REQUEST --endpoint-url http://token-generation-readmodel:8000 --region eu-central-1 + + dynamodb-admin: + image: "aaronshaf/dynamodb-admin" + container_name: dynamodb-admin + depends_on: + - token-generation-readmodel + restart: always + ports: + - "8001:8001" + environment: + - DYNAMO_ENDPOINT=http://token-generation-readmodel:8000 + - AWS_REGION=eu-west-1 # Mongo Express is a web-based MongoDB admin interface, included for convenience mongo-express: From b9363e853469cc8c57b85eabac97ba8cc99bac8e Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 5 Sep 2024 11:23:57 +0200 Subject: [PATCH 107/241] Update docker compose --- docker/docker-compose.yml | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 9d02556f87..2f60dded1a 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -68,14 +68,7 @@ services: environment: AWS_ACCESS_KEY_ID: keyid AWS_SECRET_ACCESS_KEY: key - command: dynamodb create-table - --table-name platform-states - --attribute-definitions - AttributeName=PK,AttributeType=S - --key-schema - AttributeName=PK,KeyType=HASH - --billing-mode PAY_PER_REQUEST - --endpoint-url http://token-generation-readmodel:8000 --region eu-central-1 + command: dynamodb create-table --table-name platform-states --attribute-definitions AttributeName=PK,AttributeType=S --key-schema AttributeName=PK,KeyType=HASH --billing-mode PAY_PER_REQUEST --endpoint-url http://token-generation-readmodel:8000 --region eu-central-1 token-generation-states-table-init: depends_on: @@ -85,14 +78,19 @@ services: environment: AWS_ACCESS_KEY_ID: keyid AWS_SECRET_ACCESS_KEY: key - command: dynamodb create-table - --table-name token-generation-states - --attribute-definitions - AttributeName=PK,AttributeType=S - --key-schema - AttributeName=PK,KeyType=HASH - --billing-mode PAY_PER_REQUEST - --endpoint-url http://token-generation-readmodel:8000 --region eu-central-1 + command: dynamodb create-table --table-name token-generation-states --attribute-definitions AttributeName=PK,AttributeType=S AttributeName=GSIPK_eserviceId_descriptorId,AttributeType=S --key-schema AttributeName=PK,KeyType=HASH --global-secondary-indexes "IndexName=GSIPK_eserviceId_descriptorId,KeySchema=[{AttributeName=GSIPK_eserviceId_descriptorId,KeyType=HASH}],Projection={ProjectionType=ALL}" --billing-mode PAY_PER_REQUEST --endpoint-url http://token-generation-readmodel:8000 --region eu-central-1 + + dynamodb-admin: + image: "aaronshaf/dynamodb-admin" + container_name: dynamodb-admin + depends_on: + - token-generation-readmodel + restart: always + ports: + - "8001:8001" + environment: + - DYNAMO_ENDPOINT=http://token-generation-readmodel:8000 + - AWS_REGION=eu-west-1 # Mongo Express is a web-based MongoDB admin interface, included for convenience mongo-express: From 253542bc51212c1fe63e2a43b20b3ffdb5cfa707 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 5 Sep 2024 11:43:36 +0200 Subject: [PATCH 108/241] Improvements --- ...logPlatformstateWriter.integration.test.ts | 187 +++++++----------- packages/commons-test/src/testUtils.ts | 2 +- 2 files changed, 72 insertions(+), 117 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index b1ca27dc44..ae7201a27e 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -105,7 +105,6 @@ describe("integration tests", async () => { BillingMode: "PAY_PER_REQUEST", GlobalSecondaryIndexes: [ { - // TODO: change index name IndexName: "GSIPK_eserviceId_descriptorId", KeySchema: [ { @@ -343,7 +342,7 @@ describe("integration tests", async () => { it("should throw error if previous entry exists", async () => { const tokenStateEntryPK = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: generateId(), @@ -364,7 +363,7 @@ describe("integration tests", async () => { it("should write if previous entry doesn't exist", async () => { const tokenStateEntryPK = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: generateId(), @@ -409,7 +408,7 @@ describe("integration tests", async () => { it("should return entries if they exist", async () => { const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: generateId(), @@ -425,7 +424,7 @@ describe("integration tests", async () => { const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const tokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), @@ -471,7 +470,7 @@ describe("integration tests", async () => { it("should update state if previous entries exist", async () => { const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: generateId(), @@ -488,7 +487,7 @@ describe("integration tests", async () => { const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { @@ -534,23 +533,16 @@ describe("integration tests", async () => { describe("Events V1", async () => { it("EServiceDescriptorUpdated (draft -> published)", async () => { - const draftDescriptor: Descriptor = { + const publishedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], interface: getMockDocument(), - state: descriptorState.draft, + publishedAt: new Date(), + state: descriptorState.published, }; const eservice: EService = { ...getMockEService(), - descriptors: [draftDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - // TODO: remove draftDescriptor - const publishedDescriptor: Descriptor = { - ...draftDescriptor, - publishedAt: new Date(), - state: descriptorState.published, + descriptors: [publishedDescriptor], }; const payload: EServiceDescriptorUpdatedV1 = { @@ -585,25 +577,17 @@ describe("integration tests", async () => { }); it("EServiceDescriptorUpdated (suspended -> published, version of the event is newer)", async () => { - const suspendedDescriptor: Descriptor = { + const publishedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], interface: getMockDocument(), - state: descriptorState.suspended, publishedAt: new Date(), - suspendedAt: new Date(), + suspendedAt: undefined, + state: descriptorState.published, }; const eservice: EService = { ...getMockEService(), - descriptors: [suspendedDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const publishedDescriptor: Descriptor = { - ...suspendedDescriptor, - publishedAt: new Date(), - suspendedAt: new Date(), - state: descriptorState.published, + descriptors: [publishedDescriptor], }; const payload: EServiceDescriptorUpdatedV1 = { @@ -635,7 +619,7 @@ describe("integration tests", async () => { // token-generation-states const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, @@ -652,7 +636,7 @@ describe("integration tests", async () => { const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { @@ -705,27 +689,22 @@ describe("integration tests", async () => { }); it("EServiceDescriptorUpdated (published, no operation if version of the event is lower than existing entry)", async () => { - console.log( - "EServiceDescriptorUpdated (published, no operation if version of the event is lower than existing entry)" - ); - const previousDescriptor: Descriptor = { + const publishedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], interface: getMockDocument(), - // previous state could be anything state: descriptorState.published, publishedAt: new Date(), suspendedAt: new Date(), }; const eservice: EService = { ...getMockEService(), - descriptors: [previousDescriptor], + descriptors: [publishedDescriptor], }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); const payload: EServiceDescriptorUpdatedV1 = { eserviceId: eservice.id, - eserviceDescriptor: toDescriptorV1(previousDescriptor), + eserviceDescriptor: toDescriptorV1(publishedDescriptor), }; const message: EServiceEventEnvelope = { sequence_num: 1, @@ -738,12 +717,12 @@ describe("integration tests", async () => { }; const catalogPrimaryKey = makePlatformStatesEServiceDescriptorPK({ eserviceId: eservice.id, - descriptorId: previousDescriptor.id, + descriptorId: publishedDescriptor.id, }); const previousCatalogStateEntry: PlatformStatesCatalogEntry = { PK: catalogPrimaryKey, state: itemState.inactive, - descriptorAudience: previousDescriptor.audience[0], + descriptorAudience: publishedDescriptor.audience[0], version: 2, updatedAt: new Date().toISOString(), }; @@ -752,30 +731,30 @@ describe("integration tests", async () => { // token-generation-states const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, - descriptorId: previousDescriptor.id, + descriptorId: publishedDescriptor.id, }); const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), descriptorState: itemState.inactive, - descriptorAudience: previousDescriptor.audience[0], + descriptorAudience: publishedDescriptor.audience[0], GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), descriptorState: itemState.inactive, - descriptorAudience: previousDescriptor.audience[0], + descriptorAudience: publishedDescriptor.audience[0], GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); @@ -806,23 +785,18 @@ describe("integration tests", async () => { describe("EServiceDescriptorUpdated (published -> suspended)", () => { it("should perform the update if msg.version >= existing version", async () => { - const publishedDescriptor: Descriptor = { + const suspendedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], interface: getMockDocument(), - state: descriptorState.published, publishedAt: new Date(), + suspendedAt: new Date(), + state: descriptorState.suspended, }; + const eservice: EService = { ...getMockEService(), - descriptors: [publishedDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const suspendedDescriptor: Descriptor = { - ...publishedDescriptor, - suspendedAt: new Date(), - state: descriptorState.suspended, + descriptors: [suspendedDescriptor], }; const payload: EServiceDescriptorUpdatedV1 = { @@ -845,7 +819,7 @@ describe("integration tests", async () => { const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, state: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], + descriptorAudience: suspendedDescriptor.audience[0], version: 1, updatedAt: new Date().toISOString(), }; @@ -854,30 +828,30 @@ describe("integration tests", async () => { // token-generation-states const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, + descriptorId: suspendedDescriptor.id, }); const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), descriptorState: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], + descriptorAudience: suspendedDescriptor.audience[0], GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), descriptorState: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], + descriptorAudience: suspendedDescriptor.audience[0], GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); @@ -922,23 +896,17 @@ describe("integration tests", async () => { }); it("should do nothing if msg.version < existing version", async () => { - const publishedDescriptor: Descriptor = { + const suspendedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], interface: getMockDocument(), - state: descriptorState.published, publishedAt: new Date(), + suspendedAt: new Date(), + state: descriptorState.suspended, }; const eservice: EService = { ...getMockEService(), - descriptors: [publishedDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const suspendedDescriptor: Descriptor = { - ...publishedDescriptor, - suspendedAt: new Date(), - state: descriptorState.suspended, + descriptors: [suspendedDescriptor], }; const payload: EServiceDescriptorUpdatedV1 = { @@ -961,7 +929,7 @@ describe("integration tests", async () => { const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, state: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], + descriptorAudience: suspendedDescriptor.audience[0], version: 3, updatedAt: new Date().toISOString(), }; @@ -977,23 +945,18 @@ describe("integration tests", async () => { }); it("should throw error if previous entry doesn't exist", async () => { - const publishedDescriptor: Descriptor = { + const suspendedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], interface: getMockDocument(), - state: descriptorState.published, publishedAt: new Date(), + suspendedAt: new Date(), + state: descriptorState.suspended, }; + const eservice: EService = { ...getMockEService(), - descriptors: [publishedDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const suspendedDescriptor: Descriptor = { - ...publishedDescriptor, - suspendedAt: new Date(), - state: descriptorState.suspended, + descriptors: [suspendedDescriptor], }; const payload: EServiceDescriptorUpdatedV1 = { @@ -1014,25 +977,19 @@ describe("integration tests", async () => { }); it("EServiceDescriptorUpdated (published -> archived)", async () => { - const publishedDescriptor: Descriptor = { + const archivedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], interface: getMockDocument(), - state: descriptorState.published, publishedAt: new Date(), - }; - const eservice: EService = { - ...getMockEService(), - descriptors: [publishedDescriptor], - }; - // await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - - const archivedDescriptor: Descriptor = { - ...publishedDescriptor, archivedAt: new Date(), state: descriptorState.archived, }; + const eservice: EService = { + ...getMockEService(), + descriptors: [archivedDescriptor], + }; const payload: EServiceDescriptorUpdatedV1 = { eserviceId: eservice.id, eserviceDescriptor: toDescriptorV1(archivedDescriptor), @@ -1048,12 +1005,12 @@ describe("integration tests", async () => { }; const primaryKey = makePlatformStatesEServiceDescriptorPK({ eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, + descriptorId: archivedDescriptor.id, }); const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, state: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], + descriptorAudience: archivedDescriptor.audience[0], version: 1, updatedAt: new Date().toISOString(), }; @@ -1062,30 +1019,30 @@ describe("integration tests", async () => { // token-generation-states const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, + descriptorId: archivedDescriptor.id, }); const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), descriptorState: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], + descriptorAudience: archivedDescriptor.audience[0], GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), descriptorState: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], + descriptorAudience: archivedDescriptor.audience[0], GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); @@ -1166,7 +1123,7 @@ describe("integration tests", async () => { // token-generation-states const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, @@ -1183,7 +1140,7 @@ describe("integration tests", async () => { const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { @@ -1262,7 +1219,7 @@ describe("integration tests", async () => { // token-generation-states const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, @@ -1279,7 +1236,7 @@ describe("integration tests", async () => { const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { @@ -1383,11 +1340,11 @@ describe("integration tests", async () => { }); const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { @@ -1581,11 +1538,11 @@ describe("integration tests", async () => { }); const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { @@ -1730,10 +1687,9 @@ describe("integration tests", async () => { await writeCatalogEntry(previousStateEntry, dynamoDBClient); // token-generation-states - // TODO: replace last generateId() with kid const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, @@ -1750,7 +1706,7 @@ describe("integration tests", async () => { const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { @@ -1826,10 +1782,9 @@ describe("integration tests", async () => { await writeCatalogEntry(previousStateEntry, dynamoDBClient); // token-generation-states - // TODO: replace last generateId() with kid const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, @@ -1846,7 +1801,7 @@ describe("integration tests", async () => { const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { diff --git a/packages/commons-test/src/testUtils.ts b/packages/commons-test/src/testUtils.ts index 068b9b1d9d..43bb8effd5 100644 --- a/packages/commons-test/src/testUtils.ts +++ b/packages/commons-test/src/testUtils.ts @@ -303,7 +303,7 @@ export const getMockTokenStatesClientPurposeEntry = ( tokenStateEntryPK || makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, purposeId: generateId(), }), descriptorState: itemState.inactive, From a0da40cef3a85c7fca9879af3985e785bc488854 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 5 Sep 2024 11:44:34 +0200 Subject: [PATCH 109/241] Fix --- packages/catalog-platformstate-writer/test/utils.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/utils.ts b/packages/catalog-platformstate-writer/test/utils.ts index 0d02772539..15acf0455b 100644 --- a/packages/catalog-platformstate-writer/test/utils.ts +++ b/packages/catalog-platformstate-writer/test/utils.ts @@ -15,11 +15,3 @@ export const sleep = (ms: number, mockDate = new Date()): Promise => }); // export const eservices = readModelRepository.eservices; - -export const sleep = (ms: number, mockDate = new Date()): Promise => - new Promise((resolve) => { - vi.useRealTimers(); - setTimeout(resolve, ms); - vi.useFakeTimers(); - vi.setSystemTime(mockDate); - }); From 81181569320afd5a0e6fa09245b9d86488789216 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 5 Sep 2024 11:45:03 +0200 Subject: [PATCH 110/241] Remove commented line --- packages/catalog-platformstate-writer/test/utils.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/utils.ts b/packages/catalog-platformstate-writer/test/utils.ts index 15acf0455b..0605faf7d8 100644 --- a/packages/catalog-platformstate-writer/test/utils.ts +++ b/packages/catalog-platformstate-writer/test/utils.ts @@ -13,5 +13,3 @@ export const sleep = (ms: number, mockDate = new Date()): Promise => vi.useFakeTimers(); vi.setSystemTime(mockDate); }); - -// export const eservices = readModelRepository.eservices; From 7ec1fb86b2851fb58bf9b85f15f0d191cc22516c Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 5 Sep 2024 11:45:29 +0200 Subject: [PATCH 111/241] Remove commented line --- packages/catalog-platformstate-writer/test/utils.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/utils.ts b/packages/catalog-platformstate-writer/test/utils.ts index 15acf0455b..0605faf7d8 100644 --- a/packages/catalog-platformstate-writer/test/utils.ts +++ b/packages/catalog-platformstate-writer/test/utils.ts @@ -13,5 +13,3 @@ export const sleep = (ms: number, mockDate = new Date()): Promise => vi.useFakeTimers(); vi.setSystemTime(mockDate); }); - -// export const eservices = readModelRepository.eservices; From 28b4731b52e6e0eb8fb9d6cb47df43ea19c1414d Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 5 Sep 2024 11:50:35 +0200 Subject: [PATCH 112/241] Minor improvements --- .../src/consumerServiceV2.ts | 1 - ...logPlatformstateWriter.integration.test.ts | 39 +++++++++---------- packages/commons-test/src/testUtils.ts | 2 +- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index c1eec8eae3..be93ec5e30 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -52,7 +52,6 @@ export async function handleMessageV2( existingCatalogEntryCurrent.version > msg.version ) { // Stops processing if the message is older than the catalog entry - // TODO: return or return promise return; } else if ( existingCatalogEntryCurrent && diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 518c9b128a..c45e2d3911 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -102,7 +102,6 @@ describe("integration tests", async () => { BillingMode: "PAY_PER_REQUEST", GlobalSecondaryIndexes: [ { - // TODO: change index name IndexName: "GSIPK_eserviceId_descriptorId", KeySchema: [ { @@ -340,7 +339,7 @@ describe("integration tests", async () => { it("should throw error if previous entry exists", async () => { const tokenStateEntryPK = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: generateId(), @@ -361,7 +360,7 @@ describe("integration tests", async () => { it("should write if previous entry doesn't exist", async () => { const tokenStateEntryPK = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: generateId(), @@ -406,7 +405,7 @@ describe("integration tests", async () => { it("should return entries if they exist", async () => { const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: generateId(), @@ -422,7 +421,7 @@ describe("integration tests", async () => { const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const tokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), @@ -468,7 +467,7 @@ describe("integration tests", async () => { it("should update state if previous entries exist", async () => { const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: generateId(), @@ -485,7 +484,7 @@ describe("integration tests", async () => { const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { @@ -572,7 +571,7 @@ describe("integration tests", async () => { // token-generation-states const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, @@ -589,7 +588,7 @@ describe("integration tests", async () => { const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { @@ -668,7 +667,7 @@ describe("integration tests", async () => { // token-generation-states const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, @@ -685,7 +684,7 @@ describe("integration tests", async () => { const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { @@ -789,11 +788,11 @@ describe("integration tests", async () => { }); const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { @@ -987,11 +986,11 @@ describe("integration tests", async () => { }); const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { @@ -1136,10 +1135,9 @@ describe("integration tests", async () => { await writeCatalogEntry(previousStateEntry, dynamoDBClient); // token-generation-states - // TODO: replace last generateId() with kid const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, @@ -1156,7 +1154,7 @@ describe("integration tests", async () => { const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { @@ -1232,10 +1230,9 @@ describe("integration tests", async () => { await writeCatalogEntry(previousStateEntry, dynamoDBClient); // token-generation-states - // TODO: replace last generateId() with kid const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, @@ -1252,7 +1249,7 @@ describe("integration tests", async () => { const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { diff --git a/packages/commons-test/src/testUtils.ts b/packages/commons-test/src/testUtils.ts index 068b9b1d9d..43bb8effd5 100644 --- a/packages/commons-test/src/testUtils.ts +++ b/packages/commons-test/src/testUtils.ts @@ -303,7 +303,7 @@ export const getMockTokenStatesClientPurposeEntry = ( tokenStateEntryPK || makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), - kid: generateId(), + kid: `kid ${Math.random()}`, purposeId: generateId(), }), descriptorState: itemState.inactive, From b6583553d0e33b4c0a7b2fc00b9e25a1d17204ec Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 5 Sep 2024 11:51:00 +0200 Subject: [PATCH 113/241] Update docker-compose file --- docker/docker-compose.yml | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 9d02556f87..2f60dded1a 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -68,14 +68,7 @@ services: environment: AWS_ACCESS_KEY_ID: keyid AWS_SECRET_ACCESS_KEY: key - command: dynamodb create-table - --table-name platform-states - --attribute-definitions - AttributeName=PK,AttributeType=S - --key-schema - AttributeName=PK,KeyType=HASH - --billing-mode PAY_PER_REQUEST - --endpoint-url http://token-generation-readmodel:8000 --region eu-central-1 + command: dynamodb create-table --table-name platform-states --attribute-definitions AttributeName=PK,AttributeType=S --key-schema AttributeName=PK,KeyType=HASH --billing-mode PAY_PER_REQUEST --endpoint-url http://token-generation-readmodel:8000 --region eu-central-1 token-generation-states-table-init: depends_on: @@ -85,14 +78,19 @@ services: environment: AWS_ACCESS_KEY_ID: keyid AWS_SECRET_ACCESS_KEY: key - command: dynamodb create-table - --table-name token-generation-states - --attribute-definitions - AttributeName=PK,AttributeType=S - --key-schema - AttributeName=PK,KeyType=HASH - --billing-mode PAY_PER_REQUEST - --endpoint-url http://token-generation-readmodel:8000 --region eu-central-1 + command: dynamodb create-table --table-name token-generation-states --attribute-definitions AttributeName=PK,AttributeType=S AttributeName=GSIPK_eserviceId_descriptorId,AttributeType=S --key-schema AttributeName=PK,KeyType=HASH --global-secondary-indexes "IndexName=GSIPK_eserviceId_descriptorId,KeySchema=[{AttributeName=GSIPK_eserviceId_descriptorId,KeyType=HASH}],Projection={ProjectionType=ALL}" --billing-mode PAY_PER_REQUEST --endpoint-url http://token-generation-readmodel:8000 --region eu-central-1 + + dynamodb-admin: + image: "aaronshaf/dynamodb-admin" + container_name: dynamodb-admin + depends_on: + - token-generation-readmodel + restart: always + ports: + - "8001:8001" + environment: + - DYNAMO_ENDPOINT=http://token-generation-readmodel:8000 + - AWS_REGION=eu-west-1 # Mongo Express is a web-based MongoDB admin interface, included for convenience mongo-express: From 58ae56e879682bad3a3a7e26062fc6a76c1bc409 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 5 Sep 2024 11:58:35 +0200 Subject: [PATCH 114/241] Add util test --- .../catalog-platformstate-writer/test/utils.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/catalog-platformstate-writer/test/utils.test.ts b/packages/catalog-platformstate-writer/test/utils.test.ts index e5eb3d6e7e..06f12aaf10 100644 --- a/packages/catalog-platformstate-writer/test/utils.test.ts +++ b/packages/catalog-platformstate-writer/test/utils.test.ts @@ -2,6 +2,7 @@ import { DescriptorId, EServiceId, generateId, + makeGSIPKEServiceIdDescriptorId, makePlatformStatesEServiceDescriptorPK, } from "pagopa-interop-models"; import { describe, expect, it } from "vitest"; @@ -16,4 +17,14 @@ describe("test", () => { }); expect(PK).toEqual(`ESERVICEDESCRIPTOR#${eserviceId}#${descriptorId}`); }); + + it("makeGSIPKEServiceIdDescriptorId", () => { + const eserviceId = generateId(); + const descriptorId = generateId(); + const GSI = makeGSIPKEServiceIdDescriptorId({ + eserviceId, + descriptorId, + }); + expect(GSI).toEqual(`${eserviceId}#${descriptorId}`); + }); }); From 749af6d3584ed31ca0ad0be8c6e9d08175bb6aab Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 5 Sep 2024 12:02:33 +0200 Subject: [PATCH 115/241] Add test --- .../catalog-platformstate-writer/test/utils.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/catalog-platformstate-writer/test/utils.test.ts b/packages/catalog-platformstate-writer/test/utils.test.ts index 06f12aaf10..d8bdbbb673 100644 --- a/packages/catalog-platformstate-writer/test/utils.test.ts +++ b/packages/catalog-platformstate-writer/test/utils.test.ts @@ -1,9 +1,11 @@ import { + ClientId, DescriptorId, EServiceId, generateId, makeGSIPKEServiceIdDescriptorId, makePlatformStatesEServiceDescriptorPK, + makeTokenGenerationStatesClientKidPK, } from "pagopa-interop-models"; import { describe, expect, it } from "vitest"; @@ -27,4 +29,14 @@ describe("test", () => { }); expect(GSI).toEqual(`${eserviceId}#${descriptorId}`); }); + + it("makePK token", () => { + const clientId = generateId(); + const kid = `kid ${Math.random()}`; + const GSI = makeTokenGenerationStatesClientKidPK({ + clientId, + kid, + }); + expect(GSI).toEqual(`CLIENTKID#${clientId}#${kid}`); + }); }); From 9972587eb163542859f13981846ba6f6d3473a0d Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 5 Sep 2024 12:02:41 +0200 Subject: [PATCH 116/241] Fix key --- packages/models/src/token-generation-readmodel/dynamoDB-keys.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/models/src/token-generation-readmodel/dynamoDB-keys.ts b/packages/models/src/token-generation-readmodel/dynamoDB-keys.ts index 292e4b6ebc..1566eb771f 100644 --- a/packages/models/src/token-generation-readmodel/dynamoDB-keys.ts +++ b/packages/models/src/token-generation-readmodel/dynamoDB-keys.ts @@ -91,7 +91,7 @@ export const makeTokenGenerationStatesClientKidPK = ({ clientId: ClientId; kid: string; }): TokenGenerationStatesClientKidPK => - `CLIENTKIDPURPOSE#${clientId}#${kid}` as TokenGenerationStatesClientKidPK; + `CLIENTKID#${clientId}#${kid}` as TokenGenerationStatesClientKidPK; export const GSIPKEServiceIdDescriptorId = z .string() From bceaf1cd7a2bba98eac8b462c6c9b1d2113d73ab Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 5 Sep 2024 12:03:39 +0200 Subject: [PATCH 117/241] Rename test --- packages/catalog-platformstate-writer/test/utils.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/catalog-platformstate-writer/test/utils.test.ts b/packages/catalog-platformstate-writer/test/utils.test.ts index d8bdbbb673..5f00faf92c 100644 --- a/packages/catalog-platformstate-writer/test/utils.test.ts +++ b/packages/catalog-platformstate-writer/test/utils.test.ts @@ -30,7 +30,7 @@ describe("test", () => { expect(GSI).toEqual(`${eserviceId}#${descriptorId}`); }); - it("makePK token", () => { + it("makeTokenGenerationStatesClientKidPK", () => { const clientId = generateId(); const kid = `kid ${Math.random()}`; const GSI = makeTokenGenerationStatesClientKidPK({ From b1a0d8ec17a257ca3f119e5f7127da9fd3a3d3eb Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 5 Sep 2024 12:12:23 +0200 Subject: [PATCH 118/241] Fix tests --- ...logPlatformstateWriter.integration.test.ts | 56 ++++++++++++------- .../test/utils.test.ts | 12 ++++ 2 files changed, 49 insertions(+), 19 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index c45e2d3911..248cb8151a 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -28,7 +28,7 @@ import { itemState, makeGSIPKEServiceIdDescriptorId, makePlatformStatesEServiceDescriptorPK, - makeTokenGenerationStatesClientKidPK, + makeTokenGenerationStatesClientKidPurposePK, toEServiceV2, } from "pagopa-interop-models"; import { @@ -337,9 +337,10 @@ describe("integration tests", async () => { // token-generation-states describe("writeTokenStateEntry", async () => { it("should throw error if previous entry exists", async () => { - const tokenStateEntryPK = makeTokenGenerationStatesClientKidPK({ + const tokenStateEntryPK = makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: generateId(), @@ -358,9 +359,10 @@ describe("integration tests", async () => { }); it("should write if previous entry doesn't exist", async () => { - const tokenStateEntryPK = makeTokenGenerationStatesClientKidPK({ + const tokenStateEntryPK = makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: generateId(), @@ -403,9 +405,10 @@ describe("integration tests", async () => { }); it("should return entries if they exist", async () => { - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: generateId(), @@ -419,9 +422,10 @@ describe("integration tests", async () => { }; await writeTokenStateEntry(tokenStateEntry1, dynamoDBClient); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const tokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), @@ -465,9 +469,10 @@ describe("integration tests", async () => { }); it("should update state if previous entries exist", async () => { - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: generateId(), @@ -482,9 +487,10 @@ describe("integration tests", async () => { }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { @@ -569,9 +575,10 @@ describe("integration tests", async () => { await writeCatalogEntry(previousStateEntry, dynamoDBClient); // token-generation-states - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, @@ -586,9 +593,10 @@ describe("integration tests", async () => { }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { @@ -665,9 +673,10 @@ describe("integration tests", async () => { await writeCatalogEntry(previousStateEntry, dynamoDBClient); // token-generation-states - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, @@ -682,9 +691,10 @@ describe("integration tests", async () => { }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { @@ -786,13 +796,15 @@ describe("integration tests", async () => { eserviceId: eservice.id, descriptorId: archivedDescriptor.id, }); - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { @@ -984,13 +996,15 @@ describe("integration tests", async () => { eserviceId: eservice.id, descriptorId: publishedDescriptor.id, }); - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { @@ -1135,9 +1149,10 @@ describe("integration tests", async () => { await writeCatalogEntry(previousStateEntry, dynamoDBClient); // token-generation-states - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, @@ -1152,9 +1167,10 @@ describe("integration tests", async () => { }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { @@ -1230,9 +1246,10 @@ describe("integration tests", async () => { await writeCatalogEntry(previousStateEntry, dynamoDBClient); // token-generation-states - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, @@ -1247,9 +1264,10 @@ describe("integration tests", async () => { }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { diff --git a/packages/catalog-platformstate-writer/test/utils.test.ts b/packages/catalog-platformstate-writer/test/utils.test.ts index 5f00faf92c..745a9fe922 100644 --- a/packages/catalog-platformstate-writer/test/utils.test.ts +++ b/packages/catalog-platformstate-writer/test/utils.test.ts @@ -6,6 +6,7 @@ import { makeGSIPKEServiceIdDescriptorId, makePlatformStatesEServiceDescriptorPK, makeTokenGenerationStatesClientKidPK, + PurposeId, } from "pagopa-interop-models"; import { describe, expect, it } from "vitest"; @@ -30,6 +31,17 @@ describe("test", () => { expect(GSI).toEqual(`${eserviceId}#${descriptorId}`); }); + it("makeTokenGenerationStatesClientKidPurposePK", () => { + const clientId = generateId(); + const kid = `kid ${Math.random()}`; + const purposeId = generateId(); + const GSI = makeTokenGenerationStatesClientKidPK({ + clientId, + kid, + }); + expect(GSI).toEqual(`CLIENTKIDPURPOSE#${clientId}#${kid}#${purposeId}`); + }); + it("makeTokenGenerationStatesClientKidPK", () => { const clientId = generateId(); const kid = `kid ${Math.random()}`; From bb9481dfdb251b9cba6f38c5d5402ff4e18b85dc Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 5 Sep 2024 12:18:16 +0200 Subject: [PATCH 119/241] Fix test --- packages/catalog-platformstate-writer/test/utils.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/catalog-platformstate-writer/test/utils.test.ts b/packages/catalog-platformstate-writer/test/utils.test.ts index 745a9fe922..65cc7cab5b 100644 --- a/packages/catalog-platformstate-writer/test/utils.test.ts +++ b/packages/catalog-platformstate-writer/test/utils.test.ts @@ -6,6 +6,7 @@ import { makeGSIPKEServiceIdDescriptorId, makePlatformStatesEServiceDescriptorPK, makeTokenGenerationStatesClientKidPK, + makeTokenGenerationStatesClientKidPurposePK, PurposeId, } from "pagopa-interop-models"; import { describe, expect, it } from "vitest"; @@ -35,9 +36,10 @@ describe("test", () => { const clientId = generateId(); const kid = `kid ${Math.random()}`; const purposeId = generateId(); - const GSI = makeTokenGenerationStatesClientKidPK({ + const GSI = makeTokenGenerationStatesClientKidPurposePK({ clientId, kid, + purposeId, }); expect(GSI).toEqual(`CLIENTKIDPURPOSE#${clientId}#${kid}#${purposeId}`); }); From 67b3073f1e87f3fc2689f82e4c46e1e41055982d Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Thu, 5 Sep 2024 12:19:02 +0200 Subject: [PATCH 120/241] Fix token state entry pk type --- ...logPlatformstateWriter.integration.test.ts | 127 +++++++++++------- 1 file changed, 76 insertions(+), 51 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index ae7201a27e..163f5cf42b 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -12,7 +12,6 @@ import { vi, } from "vitest"; import { - ClientId, Descriptor, EService, EServiceDescriptorActivatedV2, @@ -29,7 +28,7 @@ import { itemState, makeGSIPKEServiceIdDescriptorId, makePlatformStatesEServiceDescriptorPK, - makeTokenGenerationStatesClientKidPK, + makeTokenGenerationStatesClientKidPurposePK, toEServiceV2, } from "pagopa-interop-models"; import { @@ -340,9 +339,10 @@ describe("integration tests", async () => { // token-generation-states describe("writeTokenStateEntry", async () => { it("should throw error if previous entry exists", async () => { - const tokenStateEntryPK = makeTokenGenerationStatesClientKidPK({ + const tokenStateEntryPK = makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: generateId(), @@ -361,9 +361,10 @@ describe("integration tests", async () => { }); it("should write if previous entry doesn't exist", async () => { - const tokenStateEntryPK = makeTokenGenerationStatesClientKidPK({ + const tokenStateEntryPK = makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: generateId(), @@ -406,9 +407,10 @@ describe("integration tests", async () => { }); it("should return entries if they exist", async () => { - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: generateId(), @@ -422,9 +424,10 @@ describe("integration tests", async () => { }; await writeTokenStateEntry(tokenStateEntry1, dynamoDBClient); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const tokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), @@ -468,9 +471,10 @@ describe("integration tests", async () => { }); it("should update state if previous entries exist", async () => { - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: generateId(), @@ -485,9 +489,10 @@ describe("integration tests", async () => { }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { @@ -617,9 +622,10 @@ describe("integration tests", async () => { await writeCatalogEntry(previousStateEntry, dynamoDBClient); // token-generation-states - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, @@ -634,9 +640,10 @@ describe("integration tests", async () => { }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { @@ -729,9 +736,10 @@ describe("integration tests", async () => { await writeCatalogEntry(previousCatalogStateEntry, dynamoDBClient); // token-generation-states - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, @@ -746,9 +754,10 @@ describe("integration tests", async () => { }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { @@ -826,9 +835,10 @@ describe("integration tests", async () => { await writeCatalogEntry(previousStateEntry, dynamoDBClient); // token-generation-states - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, @@ -843,9 +853,10 @@ describe("integration tests", async () => { }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { @@ -1017,9 +1028,10 @@ describe("integration tests", async () => { await writeCatalogEntry(previousStateEntry, dynamoDBClient); // token-generation-states - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, @@ -1034,9 +1046,10 @@ describe("integration tests", async () => { }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { @@ -1121,9 +1134,10 @@ describe("integration tests", async () => { await writeCatalogEntry(previousStateEntry, dynamoDBClient); // token-generation-states - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, @@ -1138,9 +1152,10 @@ describe("integration tests", async () => { }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { @@ -1217,9 +1232,10 @@ describe("integration tests", async () => { await writeCatalogEntry(previousStateEntry, dynamoDBClient); // token-generation-states - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, @@ -1234,9 +1250,10 @@ describe("integration tests", async () => { }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { @@ -1338,13 +1355,15 @@ describe("integration tests", async () => { eserviceId: eservice.id, descriptorId: archivedDescriptor.id, }); - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { @@ -1536,13 +1555,15 @@ describe("integration tests", async () => { eserviceId: eservice.id, descriptorId: publishedDescriptor.id, }); - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { @@ -1687,9 +1708,10 @@ describe("integration tests", async () => { await writeCatalogEntry(previousStateEntry, dynamoDBClient); // token-generation-states - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, @@ -1704,9 +1726,10 @@ describe("integration tests", async () => { }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { @@ -1782,9 +1805,10 @@ describe("integration tests", async () => { await writeCatalogEntry(previousStateEntry, dynamoDBClient); // token-generation-states - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, @@ -1799,9 +1823,10 @@ describe("integration tests", async () => { }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPK({ - clientId: generateId(), + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), kid: `kid ${Math.random()}`, + purposeId: generateId(), }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { From 3d87a1492cec328ac050394a98586fba1ade7857 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 5 Sep 2024 12:20:09 +0200 Subject: [PATCH 121/241] Renaming --- packages/catalog-platformstate-writer/test/utils.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/utils.test.ts b/packages/catalog-platformstate-writer/test/utils.test.ts index 65cc7cab5b..cf1eaa6d64 100644 --- a/packages/catalog-platformstate-writer/test/utils.test.ts +++ b/packages/catalog-platformstate-writer/test/utils.test.ts @@ -36,21 +36,21 @@ describe("test", () => { const clientId = generateId(); const kid = `kid ${Math.random()}`; const purposeId = generateId(); - const GSI = makeTokenGenerationStatesClientKidPurposePK({ + const PK = makeTokenGenerationStatesClientKidPurposePK({ clientId, kid, purposeId, }); - expect(GSI).toEqual(`CLIENTKIDPURPOSE#${clientId}#${kid}#${purposeId}`); + expect(PK).toEqual(`CLIENTKIDPURPOSE#${clientId}#${kid}#${purposeId}`); }); it("makeTokenGenerationStatesClientKidPK", () => { const clientId = generateId(); const kid = `kid ${Math.random()}`; - const GSI = makeTokenGenerationStatesClientKidPK({ + const PK = makeTokenGenerationStatesClientKidPK({ clientId, kid, }); - expect(GSI).toEqual(`CLIENTKID#${clientId}#${kid}`); + expect(PK).toEqual(`CLIENTKID#${clientId}#${kid}`); }); }); From 6b1ac0861880c08f89d94d63f18243298902f276 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 5 Sep 2024 12:28:53 +0200 Subject: [PATCH 122/241] Remove no-console --- .../test/catalogPlatformstateWriter.integration.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 248cb8151a..b5e49ce809 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-floating-promises */ -/* eslint-disable no-console */ import { fail } from "assert"; import { afterAll, From 43c2d97cebfb4243cf2de447ff0c893c856fc181 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 5 Sep 2024 12:30:12 +0200 Subject: [PATCH 123/241] Remove comments --- packages/catalog-platformstate-writer/src/utils.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index a97ce6b5f2..0f591470b6 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -29,7 +29,6 @@ import { unmarshall } from "@aws-sdk/util-dynamodb"; import { z } from "zod"; import { config } from "./config/config.js"; -// TO DO scrivere test per vedere se funziona come upsert o solo come update export const writeCatalogEntry = async ( catalogEntry: PlatformStatesCatalogEntry, dynamoDBClient: DynamoDBClient @@ -271,14 +270,10 @@ export const readTokenStateEntriesByEserviceIdAndDescriptorId = async ( ExpressionAttributeValues: { ":gsi_value": { S: eserviceId_descriptorId }, }, - // ExpressionAttributeNames: { - // "#gsi": "GSIPK_eserviceId_descriptorId", - // }, ScanIndexForward: false, }; const command = new QueryCommand(input); const data: QueryCommandOutput = await dynamoDBClient.send(command); - // console.log("data.Items ", data); if (!data.Items) { throw genericInternalError( From 2b048fe6f6261defd1ed8d4c59646b1e98e9f29a Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Thu, 5 Sep 2024 12:30:37 +0200 Subject: [PATCH 124/241] Remove console.log --- packages/catalog-platformstate-writer/src/utils.ts | 2 -- .../test/catalogPlatformstateWriter.integration.test.ts | 7 ------- 2 files changed, 9 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index a97ce6b5f2..d3f9b68273 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import { descriptorState, DescriptorState, @@ -278,7 +277,6 @@ export const readTokenStateEntriesByEserviceIdAndDescriptorId = async ( }; const command = new QueryCommand(input); const data: QueryCommandOutput = await dynamoDBClient.send(command); - // console.log("data.Items ", data); if (!data.Items) { throw genericInternalError( diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 163f5cf42b..60e8d346dc 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-floating-promises */ -/* eslint-disable no-console */ import { fail } from "assert"; import { afterAll, @@ -124,10 +123,6 @@ describe("integration tests", async () => { }; const command2 = new CreateTableCommand(tokenGenerationTableDefinition); await dynamoDBClient.send(command2); - // console.log(result); - - // const tablesResult = await dynamoDBClient.listTables(); - // console.log(tablesResult.TableNames); }); afterEach(async () => { if (!config) { @@ -686,7 +681,6 @@ describe("integration tests", async () => { descriptorState: itemState.active, updatedAt: new Date().toISOString(), }; - console.log(previousTokenStateEntry1, previousTokenStateEntry2); expect(retrievedTokenStateEntries).toEqual( expect.arrayContaining([ expectedTokenStateEntry1, @@ -783,7 +777,6 @@ describe("integration tests", async () => { eserviceId_descriptorId, dynamoDBClient ); - console.log(previousTokenStateEntry1, previousTokenStateEntry2); expect(retrievedTokenStateEntries).toEqual( expect.arrayContaining([ previousTokenStateEntry1, From 6e51bd1976ff071199bd5a96c9fea2a7fb799698 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Thu, 5 Sep 2024 12:36:22 +0200 Subject: [PATCH 125/241] Remove comments --- .../src/consumerServiceV1.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index 01982fb2f0..53d9891fb9 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -25,9 +25,6 @@ export async function handleMessageV1( dynamoDBClient: DynamoDBClient ): Promise { await match(message) - // EServiceDescriptorPublished -> EServiceDescriptorUpdated - // EServiceDescriptorActivated,EServiceDescriptorSuspended -> EServiceDescriptorUpdated - // EServiceDescriptorArchived -> EServiceDescriptorUpdated .with({ type: "EServiceDescriptorUpdated" }, async (msg) => { const eserviceId = unsafeBrandId(msg.data.eserviceId); const descriptorV1 = msg.data.eserviceDescriptor; @@ -43,11 +40,6 @@ export async function handleMessageV1( }); await match(descriptor.state) .with(descriptorState.published, async () => { - // steps: - // capire se siamo in (draft -> published) o (suspened -> published) - // fare query su platform states e vedere se c'è - // se non c'è sono in draft, e continuo questa esecuzione - // se c'è (presumibilmente come inactive) allora era suspended e sono nel caso sotto (sospensione e riattivazione hanno stesso handler) const existingCatalogEntry = await readCatalogEntry( eserviceDescriptorPK, dynamoDBClient @@ -106,7 +98,6 @@ export async function handleMessageV1( } }) .with(descriptorState.suspended, async () => { - // TODO: add version check const existingCatalogEntry = await readCatalogEntry( eserviceDescriptorPK, dynamoDBClient From 374d3ec6408c0eea0b8a57088a1249653c15a62c Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 5 Sep 2024 12:36:42 +0200 Subject: [PATCH 126/241] Minor improvement --- ...logPlatformstateWriter.integration.test.ts | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index b5e49ce809..37d98ceb07 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -422,7 +422,7 @@ describe("integration tests", async () => { await writeTokenStateEntry(tokenStateEntry1, dynamoDBClient); const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), + clientId: generateId(), kid: `kid ${Math.random()}`, purposeId: generateId(), }); @@ -469,7 +469,7 @@ describe("integration tests", async () => { it("should update state if previous entries exist", async () => { const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), + clientId: generateId(), kid: `kid ${Math.random()}`, purposeId: generateId(), }); @@ -487,7 +487,7 @@ describe("integration tests", async () => { await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), + clientId: generateId(), kid: `kid ${Math.random()}`, purposeId: generateId(), }); @@ -575,7 +575,7 @@ describe("integration tests", async () => { // token-generation-states const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), + clientId: generateId(), kid: `kid ${Math.random()}`, purposeId: generateId(), }); @@ -593,7 +593,7 @@ describe("integration tests", async () => { await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), + clientId: generateId(), kid: `kid ${Math.random()}`, purposeId: generateId(), }); @@ -673,7 +673,7 @@ describe("integration tests", async () => { // token-generation-states const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), + clientId: generateId(), kid: `kid ${Math.random()}`, purposeId: generateId(), }); @@ -691,7 +691,7 @@ describe("integration tests", async () => { await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), + clientId: generateId(), kid: `kid ${Math.random()}`, purposeId: generateId(), }); @@ -796,12 +796,12 @@ describe("integration tests", async () => { descriptorId: archivedDescriptor.id, }); const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), + clientId: generateId(), kid: `kid ${Math.random()}`, purposeId: generateId(), }); const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), + clientId: generateId(), kid: `kid ${Math.random()}`, purposeId: generateId(), }); @@ -996,12 +996,12 @@ describe("integration tests", async () => { descriptorId: publishedDescriptor.id, }); const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), + clientId: generateId(), kid: `kid ${Math.random()}`, purposeId: generateId(), }); const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), + clientId: generateId(), kid: `kid ${Math.random()}`, purposeId: generateId(), }); @@ -1149,7 +1149,7 @@ describe("integration tests", async () => { // token-generation-states const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), + clientId: generateId(), kid: `kid ${Math.random()}`, purposeId: generateId(), }); @@ -1167,7 +1167,7 @@ describe("integration tests", async () => { await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), + clientId: generateId(), kid: `kid ${Math.random()}`, purposeId: generateId(), }); @@ -1246,7 +1246,7 @@ describe("integration tests", async () => { // token-generation-states const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), + clientId: generateId(), kid: `kid ${Math.random()}`, purposeId: generateId(), }); @@ -1264,7 +1264,7 @@ describe("integration tests", async () => { await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), + clientId: generateId(), kid: `kid ${Math.random()}`, purposeId: generateId(), }); From 4e8a3623ec8b5ece40522809b0f9c8d63a26d1d6 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 5 Sep 2024 12:36:56 +0200 Subject: [PATCH 127/241] Fix import --- .../test/catalogPlatformstateWriter.integration.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 37d98ceb07..3a6e29e534 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -11,7 +11,6 @@ import { vi, } from "vitest"; import { - ClientId, Descriptor, EService, EServiceDescriptorActivatedV2, From a6d8d65f390a862df70e655dddec40e887e458d1 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 5 Sep 2024 15:03:41 +0200 Subject: [PATCH 128/241] Add api spec --- .../open-api/authorizationServerApi.yml | 215 ++++++++++++++++++ packages/api-clients/src/index.ts | 1 + 2 files changed, 216 insertions(+) create mode 100644 packages/api-clients/open-api/authorizationServerApi.yml diff --git a/packages/api-clients/open-api/authorizationServerApi.yml b/packages/api-clients/open-api/authorizationServerApi.yml new file mode 100644 index 0000000000..3fcbf37b1e --- /dev/null +++ b/packages/api-clients/open-api/authorizationServerApi.yml @@ -0,0 +1,215 @@ +openapi: 3.0.3 +info: + title: Interoperability Authorization Server Micro Service + description: Provides endpoints to request an interoperability token + version: "0.1.0" + contact: + name: API Support + url: "http://www.example.com/support" + email: support@example.com + termsOfService: "http://swagger.io/terms/" + x-api-id: an x-api-id + x-summary: an x-summary +servers: + - url: "/authorization-server" + description: Interoperability Authorization Server +tags: + - name: auth + description: Get security information + externalDocs: + description: Find out more + url: http://swagger.io + - name: health + description: Verify service status + externalDocs: + description: Find out more + url: http://swagger.io +paths: + "/token.oauth2": + post: + tags: + - auth + summary: Create a new access token + description: Return the generated access token + operationId: createToken + requestBody: + required: true + content: + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/AccessTokenRequest" + responses: + "200": + description: The Access token + headers: + Cache-Control: + schema: + type: string + default: no-cache, no-store + description: no-cache, no-store + "X-Rate-Limit-Limit": + schema: + type: integer + description: Max allowed requests within time interval + "X-Rate-Limit-Remaining": + schema: + type: integer + description: Remaining requests within time interval + "X-Rate-Limit-Interval": + schema: + type: integer + description: Time interval in milliseconds. Allowed requests will be constantly replenished during the interval. At the end of the interval the max allowed requests will be available + content: + application/json: + schema: + $ref: "#/components/schemas/ClientCredentialsResponse" + "400": + description: Bad request + x-noqa: RFC6749 + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + "401": + description: Unauthorized + x-noqa: RFC6749 + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + "429": + description: Too Many Requests + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + headers: + "X-Rate-Limit-Limit": + schema: + type: integer + description: Max allowed requests within time interval + "X-Rate-Limit-Remaining": + schema: + type: integer + description: Remaining requests within time interval + "X-Rate-Limit-Interval": + schema: + type: integer + description: Time interval in milliseconds. Allowed requests will be constantly replenished during the interval. At the end of the interval the max allowed requests will be available + /status: + get: + security: [] + summary: Returns the application status + description: Returns the application status + operationId: get_status + tags: + - health + responses: + "200": + description: This is the valid status from the server. + content: + application/problem+json: + schema: + $ref: "#/components/schemas/Problem" +components: + schemas: + AccessTokenRequest: + type: object + required: + - client_assertion + - client_assertion_type + - grant_type + properties: + client_id: + type: string + example: e58035ce-c753-4f72-b613-46f8a17b71cc + client_assertion: + type: string + format: jws + client_assertion_type: + type: string + example: urn:ietf:params:oauth:client-assertion-type:jwt-bearer + grant_type: + type: string + enum: + - client_credentials + TokenType: + type: string + description: Represents the token type + enum: + - Bearer + ClientCredentialsResponse: + type: object + required: + - access_token + - token_type + - expires_in + properties: + access_token: + type: string + format: jws + token_type: + $ref: "#/components/schemas/TokenType" + expires_in: + type: integer + format: int32 + maximum: 600 + Problem: + properties: + type: + description: URI reference of type definition + type: string + status: + description: The HTTP status code generated by the origin server for this occurrence of the problem. + example: 400 + exclusiveMaximum: true + format: int32 + maximum: 600 + minimum: 100 + type: integer + title: + description: A short, summary of the problem type. Written in english and readable + example: Service Unavailable + maxLength: 64 + pattern: "^[ -~]{0,64}$" + type: string + correlationId: + description: Unique identifier of the request + example: "53af4f2d-0c87-41ef-a645-b726a821852b" + maxLength: 64 + type: string + detail: + description: A human readable explanation of the problem. + example: Request took too long to complete. + maxLength: 4096 + pattern: "^.{0,1024}$" + type: string + errors: + type: array + minItems: 0 + items: + $ref: "#/components/schemas/ProblemError" + additionalProperties: false + required: + - type + - status + - title + - errors + ProblemError: + properties: + code: + description: Internal code of the error + example: 123-4567 + minLength: 8 + maxLength: 8 + pattern: "^[0-9]{3}-[0-9]{4}$" + type: string + detail: + description: A human readable explanation specific to this occurrence of the problem. + example: Parameter not valid + maxLength: 4096 + pattern: "^.{0,1024}$" + type: string + required: + - code + - detail diff --git a/packages/api-clients/src/index.ts b/packages/api-clients/src/index.ts index e5ada0a70e..7bb23138a5 100644 --- a/packages/api-clients/src/index.ts +++ b/packages/api-clients/src/index.ts @@ -8,3 +8,4 @@ export * as purposeApi from "./generated/purposeApi.js"; export * as selfcareV2ClientApi from "./generated/selfcareV2ClientApi.js"; export * as tenantApi from "./generated/tenantApi.js"; export * from "./selfcareClients.js"; +export * as authorizationServerApi from "./generated/authorizationServerApi.js"; From b414e180b19fbca0828813eecbf8d431f189f879 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Thu, 5 Sep 2024 17:48:15 +0200 Subject: [PATCH 129/241] Adjust types --- .../src/consumerServiceV1.ts | 8 ++++---- .../src/consumerServiceV2.ts | 8 ++++---- packages/catalog-platformstate-writer/src/utils.ts | 6 ++---- .../token-generation-states-entry.ts | 13 +++++++++++-- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index 53d9891fb9..919f27b078 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -13,7 +13,7 @@ import { import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; import { deleteCatalogEntry, - descriptorStateToClientState, + descriptorStateToItemState, readCatalogEntry, updateDescriptorStateInPlatformStatesEntry, updateDescriptorStateInTokenGenerationStatesTable, @@ -60,7 +60,7 @@ export async function handleMessageV1( await updateDescriptorStateInPlatformStatesEntry( dynamoDBClient, eserviceDescriptorPK, - descriptorStateToClientState(descriptor.state), + descriptorStateToItemState(descriptor.state), msg.version ); @@ -77,7 +77,7 @@ export async function handleMessageV1( } else { const catalogEntry: PlatformStatesCatalogEntry = { PK: eserviceDescriptorPK, - state: descriptorStateToClientState(descriptor.state), + state: descriptorStateToItemState(descriptor.state), descriptorAudience: descriptor.audience[0], version: msg.version, updatedAt: new Date().toISOString(), @@ -118,7 +118,7 @@ export async function handleMessageV1( await updateDescriptorStateInPlatformStatesEntry( dynamoDBClient, eserviceDescriptorPK, - descriptorStateToClientState(descriptor.state), + descriptorStateToItemState(descriptor.state), msg.version ); diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index be93ec5e30..1a14be2ec1 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -16,7 +16,7 @@ import { import { match } from "ts-pattern"; import { deleteCatalogEntry, - descriptorStateToClientState, + descriptorStateToItemState, readCatalogEntry, updateDescriptorStateInPlatformStatesEntry, updateDescriptorStateInTokenGenerationStatesTable, @@ -60,7 +60,7 @@ export async function handleMessageV2( await updateDescriptorStateInPlatformStatesEntry( dynamoDBClient, primaryKeyCurrent, - descriptorStateToClientState(descriptor.state), + descriptorStateToItemState(descriptor.state), msg.version ); @@ -77,7 +77,7 @@ export async function handleMessageV2( } else { const catalogEntry: PlatformStatesCatalogEntry = { PK: primaryKeyCurrent, - state: descriptorStateToClientState(descriptor.state), + state: descriptorStateToItemState(descriptor.state), descriptorAudience: descriptor.audience[0], version: msg.version, updatedAt: new Date().toISOString(), @@ -153,7 +153,7 @@ export async function handleMessageV2( await updateDescriptorStateInPlatformStatesEntry( dynamoDBClient, primaryKey, - descriptorStateToClientState(descriptor.state), + descriptorStateToItemState(descriptor.state), msg.version ); diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index d8d957e415..c30db4cac8 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -114,9 +114,7 @@ export const readAllItems = async ( }; */ -export const descriptorStateToClientState = ( - state: DescriptorState -): ItemState => +export const descriptorStateToItemState = (state: DescriptorState): ItemState => state === descriptorState.published || state === descriptorState.deprecated ? itemState.active : itemState.inactive; @@ -317,7 +315,7 @@ export const updateDescriptorStateInTokenGenerationStatesTable = async ( }, ExpressionAttributeValues: { ":newState": { - S: descriptorStateToClientState(descriptorState), + S: descriptorStateToItemState(descriptorState), }, ":newUpdateAt": { S: new Date().toISOString(), diff --git a/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts b/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts index e35c5f5045..ab7ce140fc 100644 --- a/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts +++ b/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts @@ -1,5 +1,4 @@ import { z } from "zod"; -import { ClientKind } from "../authorization/client.js"; import { AgreementId, ClientId, @@ -16,9 +15,19 @@ import { GSIPKClientIdPurposeId, } from "./dynamoDB-keys.js"; +export const clientKindTokenStates = { + consumer: "CONSUMER", + api: "API", +} as const; +export const ClientKindTokenStates = z.enum([ + Object.values(clientKindTokenStates)[0], + ...Object.values(clientKindTokenStates).slice(1), +]); +export type ClientKindTokenStates = z.infer; + const TokenGenerationStatesBaseEntry = z.object({ consumerId: TenantId, - clientKind: ClientKind, + clientKind: ClientKindTokenStates, publicKey: z.string(), GSIPK_clientId: ClientId, GSIPK_kid: z.string(), From 6470dcf214b43295257b9050480ab3d45d1615b5 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Thu, 5 Sep 2024 17:51:00 +0200 Subject: [PATCH 130/241] Fix client kind type --- packages/commons-test/src/testUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/commons-test/src/testUtils.ts b/packages/commons-test/src/testUtils.ts index 43bb8effd5..e25a4acf1a 100644 --- a/packages/commons-test/src/testUtils.ts +++ b/packages/commons-test/src/testUtils.ts @@ -43,6 +43,7 @@ import { makeGSIPKEServiceIdDescriptorId, TokenGenerationStatesClientKidPurposePK, makeTokenGenerationStatesClientKidPurposePK, + clientKindTokenStates, } from "pagopa-interop-models"; import { AuthData } from "pagopa-interop-commons"; import { z } from "zod"; @@ -316,7 +317,7 @@ export const getMockTokenStatesClientPurposeEntry = ( consumerId: generateId(), eserviceId: generateId(), }), - clientKind: clientKind.consumer, + clientKind: clientKindTokenStates.consumer, publicKey: "PEM", GSIPK_clientId: generateId(), GSIPK_kid: "KID", From 560330b53d31784b5cc751a1e8d84fd23886ee58 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Thu, 5 Sep 2024 18:10:26 +0200 Subject: [PATCH 131/241] Fix tests --- .../test/catalogPlatformstateWriter.integration.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 60e8d346dc..606d578eb0 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -53,7 +53,7 @@ import { z } from "zod"; import { handleMessageV1 } from "../src/consumerServiceV1.js"; import { deleteCatalogEntry, - descriptorStateToClientState, + descriptorStateToItemState, readCatalogEntry, readTokenStateEntriesByEserviceIdAndDescriptorId, updateDescriptorStateInPlatformStatesEntry, @@ -314,11 +314,11 @@ describe("integration tests", async () => { }); }); - describe("descriptorStateToClientState", async () => { + describe("descriptorStateToItemState", async () => { it.each([descriptorState.published, descriptorState.deprecated])( "should convert %s state to active", async (s) => { - expect(descriptorStateToClientState(s)).toBe(itemState.active); + expect(descriptorStateToItemState(s)).toBe(itemState.active); } ); @@ -327,7 +327,7 @@ describe("integration tests", async () => { descriptorState.draft, descriptorState.suspended, ])("should convert %s state to inactive", async (s) => { - expect(descriptorStateToClientState(s)).toBe(itemState.inactive); + expect(descriptorStateToItemState(s)).toBe(itemState.inactive); }); }); From 535e6de332ef9f54acf853e36a790bc48db151d4 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 6 Sep 2024 11:20:37 +0200 Subject: [PATCH 132/241] Draft --- .../client-assertion-validation/package.json | 37 ++ .../client-assertion-validation/src/index.ts | 1 + .../client-assertion-validation/src/types.ts | 58 ++++ .../client-assertion-validation/src/utils.ts | 139 ++++++++ .../src/validation.ts | 51 +++ .../test/sample.test.ts | 49 +++ .../test/vitestGlobalSetup.ts | 3 + .../tsconfig.check.json | 7 + .../client-assertion-validation/tsconfig.json | 9 + .../vitest.config.ts | 11 + pnpm-lock.yaml | 322 ++++++++++-------- 11 files changed, 546 insertions(+), 141 deletions(-) create mode 100644 packages/client-assertion-validation/package.json create mode 100644 packages/client-assertion-validation/src/index.ts create mode 100644 packages/client-assertion-validation/src/types.ts create mode 100644 packages/client-assertion-validation/src/utils.ts create mode 100644 packages/client-assertion-validation/src/validation.ts create mode 100644 packages/client-assertion-validation/test/sample.test.ts create mode 100644 packages/client-assertion-validation/test/vitestGlobalSetup.ts create mode 100644 packages/client-assertion-validation/tsconfig.check.json create mode 100644 packages/client-assertion-validation/tsconfig.json create mode 100644 packages/client-assertion-validation/vitest.config.ts diff --git a/packages/client-assertion-validation/package.json b/packages/client-assertion-validation/package.json new file mode 100644 index 0000000000..38ea2cefcb --- /dev/null +++ b/packages/client-assertion-validation/package.json @@ -0,0 +1,37 @@ +{ + "name": "pagopa-interop-client-assertion-validation", + "private": true, + "version": "1.0.0", + "description": "PagoPA Interoperability utility to validate client assertion", + "main": "dist", + "type": "module", + "exports": { + ".": "./dist/index.js" + }, + "scripts": { + "test": "vitest", + "lint": "eslint . --ext .ts,.tsx", + "lint:autofix": "eslint . --ext .ts,.tsx --fix", + "format:check": "prettier --check src", + "format:write": "prettier --write src", + "build": "tsc", + "check": "tsc --project tsconfig.check.json" + }, + "license": "Apache-2.0", + "dependencies": { + "jsonwebtoken": "9.0.2", + "pagopa-interop-api-clients": "workspace:*", + "pagopa-interop-commons-test": "workspace:*", + "pagopa-interop-models": "workspace:*", + "ts-pattern": "5.2.0", + "zod": "3.23.8" + }, + "devDependencies": { + "@types/jsonwebtoken": "9.0.6", + "@types/node": "20.14.6", + "eslint": "8.57.0", + "prettier": "2.8.8", + "typescript": "5.4.5", + "vitest": "1.6.0" + } +} diff --git a/packages/client-assertion-validation/src/index.ts b/packages/client-assertion-validation/src/index.ts new file mode 100644 index 0000000000..ecd7a679ec --- /dev/null +++ b/packages/client-assertion-validation/src/index.ts @@ -0,0 +1 @@ +export * from "./validation.js"; diff --git a/packages/client-assertion-validation/src/types.ts b/packages/client-assertion-validation/src/types.ts new file mode 100644 index 0000000000..f6eba0ec1c --- /dev/null +++ b/packages/client-assertion-validation/src/types.ts @@ -0,0 +1,58 @@ +import { authorizationManagementApi } from "pagopa-interop-api-clients"; +import { + AgreementId, + ClientId, + clientKind, + EServiceId, + PurposeId, + TenantId, +} from "pagopa-interop-models"; +import { z } from "zod"; + +export const ClientAssertionHeader = z.object({ + kid: z.string(), + alg: z.string(), // TODO Enum +}); +export type ClientAssertionHeader = z.infer; + +export const ClientAssertionPayload = z.object({ + sub: z.string(), + jti: z.string(), + iat: z.number(), + iss: z.string(), + aud: z.array(z.string()), + exp: z.number(), + purposeId: PurposeId.optional(), +}); +export type ClientAssertionPayload = z.infer; + +export const ClientAssertion = z.object({ + header: ClientAssertionHeader, + payload: ClientAssertionPayload, +}); +export type ClientAssertion = z.infer; + +export const Key = z.object({ + GSIPK_clientId: ClientId, + consumerId: TenantId, + kidWithPurposeId: z.string(), // TO DO which field of the table is mapper to this? + publicKey: z.string().min(1), + algorithm: z.literal("RS256"), // no field to map from the table. Is it included extracted from publicKey field? +}); +export type Key = z.infer; + +export const ConsumerKey = Key.extend({ + clientKind: z.literal(clientKind.consumer), + GSIPK_purposeId: PurposeId, // to do is this naming ok? + purposeState: authorizationManagementApi.ClientComponentState, + agreementId: AgreementId, + agreementState: authorizationManagementApi.ClientComponentState, + eServiceId: EServiceId, // no field to map. Extract from GSIPK_eserviceId_descriptorId? + descriptorState: authorizationManagementApi.ClientComponentState, +}); +export type ConsumerKey = z.infer; + +export const ApiKey = Key.extend({ + clientKind: z.literal(clientKind.api), +}); +export type ApiKey = z.infer; diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts new file mode 100644 index 0000000000..a1a66ec648 --- /dev/null +++ b/packages/client-assertion-validation/src/utils.ts @@ -0,0 +1,139 @@ +import { authorizationServerApi } from "pagopa-interop-api-clients"; +import { decode, JwtPayload, verify } from "jsonwebtoken"; +import { PurposeId } from "pagopa-interop-models"; +import { ClientAssertion, ConsumerKey, Key } from "./types.js"; +const CLIENT_ASSERTION_AUDIENCE = ""; + +export const validateRequestParameters = ( + request: authorizationServerApi.AccessTokenRequest +): boolean => { + const expectedClientAssertionType: string = + "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; + const expectedClientCredentialsGrantType: string = "client_credentials"; + + if (request.client_assertion_type !== expectedClientAssertionType) { + throw Error( + `Unexpected client assertion type. Received ${request.client_assertion_type}` + ); + } else if (request.grant_type !== expectedClientCredentialsGrantType) { + throw Error(`Unexpected grant type. Received ${request.grant_type}`); + } + + return true; +}; + +export const verifyClientAssertion = ( + clientAssertionJws: string, + clientId: string | undefined +): ClientAssertion => { + const decoded = decode(clientAssertionJws, { complete: true, json: true }); + + const validateAudience = (aud: string | string[] | undefined): string[] => { + if (aud === CLIENT_ASSERTION_AUDIENCE) { + return [aud]; + } + + if (!Array.isArray(aud)) { + throw Error("Audience must be an array"); + } + + if (!aud.includes(CLIENT_ASSERTION_AUDIENCE)) { + throw Error("Unexpected client assertion audience"); + } + + return aud; + }; + + if (!decoded) { + throw Error("Invalid format for Client assertion"); + } + + if (typeof decoded.payload === "string") { + throw Error("Unexpected client assertion payload"); + } + + if (!decoded.payload.jti) { + throw Error("JTI must be present in client assertion header"); + } + + if (!decoded.payload.iat) { + throw Error("IAT must be present in client assertion header"); + } + + if (!decoded.payload.exp) { + throw Error("EXP must be present in client assertion header"); + } + + if (!decoded.payload.iss) { + throw Error("ISS must be present in client assertion header"); + } + + if (!decoded.payload.sub) { + throw Error("Subject is mandatory in client assertion"); + } + + if (clientId && decoded.payload.sub !== clientId) { + throw Error("Client Id must be equal to client assertion subject"); + } + + if ( + decoded.payload.purposeId && + !PurposeId.safeParse(decoded.payload.purposeId).success + ) { + throw Error("Wrong purpose id format in client assertion"); + } + + if (!decoded.header.kid) { + throw Error("Kid must be present in client assertion header"); + } + + return { + header: { + kid: decoded.header.kid, + alg: decoded.header.alg, + }, + payload: { + sub: decoded.payload.sub, + purposeId: decoded.payload.purposeId, + jti: decoded.payload.jti, + iat: decoded.payload.iat, + iss: decoded.payload.iss, + aud: validateAudience(decoded.payload.aud), + exp: decoded.payload.exp, // TODO Check unit of measure + }, + }; +}; + +export const b64Decode = (str: string): string => + Buffer.from(str, "base64").toString("binary"); + +export const verifyClientAssertionSignature = ( + clientAssertionJws: string, + key: Key +): JwtPayload => { + const result = verify(clientAssertionJws, b64Decode(key.publicKey), { + algorithms: [key.algorithm], + }); + + // TODO Improve this + if (typeof result === "string") { + throw Error("Unexpected assertion verification result"); + } else { + return result; + } + // TODO Handle error codes. See https://github.com/auth0/node-jsonwebtoken#errors--codes + + // to do should it be a try/catch? +}; + +export const assertValidPlatformState = (key: ConsumerKey): void => { + if (key.agreementState !== "ACTIVE") { + throw Error("Invalid agreement state"); + } + if (key.descriptorState !== "ACTIVE") { + throw Error("Invalid eservice state"); + } + if (key.purposeState !== "ACTIVE") { + throw Error("Invalid purpose state"); + } +}; diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts new file mode 100644 index 0000000000..8fba5e0cf4 --- /dev/null +++ b/packages/client-assertion-validation/src/validation.ts @@ -0,0 +1,51 @@ +import { authorizationServerApi } from "pagopa-interop-api-clients"; +import { match } from "ts-pattern"; +import { clientKind } from "pagopa-interop-models"; +import { + verifyClientAssertionSignature, + validateRequestParameters, + assertValidPlatformState, + verifyClientAssertion, +} from "./utils.js"; +import { ApiKey, ClientAssertion, ConsumerKey } from "./types.js"; + +export const assertValidClientAssertion = async ( + request: authorizationServerApi.AccessTokenRequest, + key: ConsumerKey | ApiKey // To do use just Key? +): Promise => { + validateRequestParameters(request); + + const clientAssertionJWT = verifyClientAssertion( + request.client_assertion, + request.client_id + ); + + verifyClientAssertionSignature(request.client_assertion, key); + + if (ApiKey.safeParse(key).success) { + return clientAssertionJWT; + } + + match(key.clientKind) + .with(clientKind.api, () => { + if (!ApiKey.safeParse(key).success) { + // to do: useful? + + throw Error("parsing"); + } + return true; + }) + .with(clientKind.consumer, () => { + if (ConsumerKey.safeParse(key).success) { + // to do: useful? + + assertValidPlatformState(key as ConsumerKey); + } else { + throw Error("parsing"); + } + return true; + }) + .exhaustive(); + + return clientAssertionJWT; +}; diff --git a/packages/client-assertion-validation/test/sample.test.ts b/packages/client-assertion-validation/test/sample.test.ts new file mode 100644 index 0000000000..7dcb245cd9 --- /dev/null +++ b/packages/client-assertion-validation/test/sample.test.ts @@ -0,0 +1,49 @@ +import { describe, it } from "vitest"; +import { clientKind, generateId } from "pagopa-interop-models"; +import { ConsumerKey, Key, ApiKey } from ".././src/types.js"; + +describe("test", () => { + it("zod inheritance", () => { + const key: Key = { + clientId: generateId(), + consumerId: generateId(), + kidWithPurposeId: "123", + publicKey: "123", + algorithm: "RS256", + }; + + const cKey: ConsumerKey = { + ...key, + clientKind: "Consumer", + purposeId: generateId(), + purposeState: "ACTIVE", + agreementId: generateId(), + agreementState: "ACTIVE", + eServiceId: generateId(), + eServiceState: "ACTIVE", + }; + + const aKey: ApiKey = { + ...key, + clientKind: clientKind.api, + }; + + doStuff(cKey); + doStuff(aKey); + doStuff(key); + }); +}); + +const doStuff = (input: Key): void => { + // console.log(typeof input); + // const res = ConsumerKey.safeParse(input); + // console.log(res.error); + if (ConsumerKey.safeParse(input).success) { + console.log("consumer"); + } else if (ApiKey.safeParse(input).success) { + console.log("api"); + } else { + console.log("no idea"); + } + console.log(input); +}; diff --git a/packages/client-assertion-validation/test/vitestGlobalSetup.ts b/packages/client-assertion-validation/test/vitestGlobalSetup.ts new file mode 100644 index 0000000000..85a4c8ea41 --- /dev/null +++ b/packages/client-assertion-validation/test/vitestGlobalSetup.ts @@ -0,0 +1,3 @@ +import { setupTestContainersVitestGlobal } from "pagopa-interop-commons-test/index.js"; + +export default setupTestContainersVitestGlobal(); diff --git a/packages/client-assertion-validation/tsconfig.check.json b/packages/client-assertion-validation/tsconfig.check.json new file mode 100644 index 0000000000..a19f84bcb7 --- /dev/null +++ b/packages/client-assertion-validation/tsconfig.check.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "noEmit": true, + }, + "include": ["src", "test"] +} diff --git a/packages/client-assertion-validation/tsconfig.json b/packages/client-assertion-validation/tsconfig.json new file mode 100644 index 0000000000..2b7c7808f3 --- /dev/null +++ b/packages/client-assertion-validation/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "outDir": "dist", + }, + "include": ["src"] +} diff --git a/packages/client-assertion-validation/vitest.config.ts b/packages/client-assertion-validation/vitest.config.ts new file mode 100644 index 0000000000..9ece1be991 --- /dev/null +++ b/packages/client-assertion-validation/vitest.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globalSetup: ["./test/vitestGlobalSetup.ts"], + testTimeout: 60000, + hookTimeout: 60000, + fileParallelism: false, + pool: "forks", + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45285d2c77..ee5d370c2e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1032,6 +1032,46 @@ importers: specifier: 1.6.0 version: 1.6.0(@types/node@20.14.6) + packages/client-assertion-validation: + dependencies: + jsonwebtoken: + specifier: 9.0.2 + version: 9.0.2 + pagopa-interop-api-clients: + specifier: workspace:* + version: link:../api-clients + pagopa-interop-commons-test: + specifier: workspace:* + version: link:../commons-test + pagopa-interop-models: + specifier: workspace:* + version: link:../models + ts-pattern: + specifier: 5.2.0 + version: 5.2.0 + zod: + specifier: 3.23.8 + version: 3.23.8 + devDependencies: + '@types/jsonwebtoken': + specifier: 9.0.6 + version: 9.0.6 + '@types/node': + specifier: 20.14.6 + version: 20.14.6 + eslint: + specifier: 8.57.0 + version: 8.57.0 + prettier: + specifier: 2.8.8 + version: 2.8.8 + typescript: + specifier: 5.4.5 + version: 5.4.5 + vitest: + specifier: 1.6.0 + version: 1.6.0(@types/node@20.14.6) + packages/client-readmodel-writer: dependencies: '@protobuf-ts/runtime': @@ -2089,7 +2129,7 @@ packages: dependencies: '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.598.0 + '@aws-sdk/types': 3.609.0 '@aws-sdk/util-locate-window': 3.568.0 '@smithy/util-utf8': 2.3.0 tslib: 2.6.3 @@ -2165,28 +2205,28 @@ packages: '@aws-sdk/util-endpoints': 3.609.0 '@aws-sdk/util-user-agent-browser': 3.609.0 '@aws-sdk/util-user-agent-node': 3.609.0 - '@smithy/config-resolver': 3.0.4 - '@smithy/core': 2.2.4 - '@smithy/fetch-http-handler': 3.2.0 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 '@smithy/hash-node': 3.0.3 '@smithy/invalid-dependency': 3.0.3 - '@smithy/middleware-content-length': 3.0.3 - '@smithy/middleware-endpoint': 3.0.4 - '@smithy/middleware-retry': 3.0.7 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 '@smithy/middleware-serde': 3.0.3 '@smithy/middleware-stack': 3.0.3 - '@smithy/node-config-provider': 3.1.3 - '@smithy/node-http-handler': 3.1.1 - '@smithy/protocol-http': 4.0.3 - '@smithy/smithy-client': 3.1.5 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 '@smithy/types': 3.3.0 '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.7 - '@smithy/util-defaults-mode-node': 3.0.7 - '@smithy/util-endpoints': 2.0.4 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 '@smithy/util-middleware': 3.0.3 '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 @@ -2325,36 +2365,36 @@ packages: '@aws-sdk/util-user-agent-browser': 3.598.0 '@aws-sdk/util-user-agent-node': 3.598.0 '@aws-sdk/xml-builder': 3.598.0 - '@smithy/config-resolver': 3.0.4 - '@smithy/core': 2.2.4 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 '@smithy/eventstream-serde-browser': 3.0.4 '@smithy/eventstream-serde-config-resolver': 3.0.3 '@smithy/eventstream-serde-node': 3.0.4 - '@smithy/fetch-http-handler': 3.2.0 + '@smithy/fetch-http-handler': 3.2.4 '@smithy/hash-blob-browser': 3.1.2 '@smithy/hash-node': 3.0.3 '@smithy/hash-stream-node': 3.1.2 '@smithy/invalid-dependency': 3.0.3 '@smithy/md5-js': 3.0.3 - '@smithy/middleware-content-length': 3.0.3 - '@smithy/middleware-endpoint': 3.0.4 - '@smithy/middleware-retry': 3.0.7 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 '@smithy/middleware-serde': 3.0.3 '@smithy/middleware-stack': 3.0.3 - '@smithy/node-config-provider': 3.1.3 - '@smithy/node-http-handler': 3.1.1 - '@smithy/protocol-http': 4.0.3 - '@smithy/smithy-client': 3.1.5 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 '@smithy/types': 3.3.0 '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.7 - '@smithy/util-defaults-mode-node': 3.0.7 - '@smithy/util-endpoints': 2.0.4 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 '@smithy/util-retry': 3.0.3 - '@smithy/util-stream': 3.0.5 + '@smithy/util-stream': 3.1.3 '@smithy/util-utf8': 3.0.0 '@smithy/util-waiter': 3.1.2 tslib: 2.6.3 @@ -2431,28 +2471,28 @@ packages: '@aws-sdk/util-endpoints': 3.598.0 '@aws-sdk/util-user-agent-browser': 3.598.0 '@aws-sdk/util-user-agent-node': 3.598.0 - '@smithy/config-resolver': 3.0.4 - '@smithy/core': 2.2.4 - '@smithy/fetch-http-handler': 3.2.0 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 '@smithy/hash-node': 3.0.3 '@smithy/invalid-dependency': 3.0.3 - '@smithy/middleware-content-length': 3.0.3 - '@smithy/middleware-endpoint': 3.0.4 - '@smithy/middleware-retry': 3.0.7 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 '@smithy/middleware-serde': 3.0.3 '@smithy/middleware-stack': 3.0.3 - '@smithy/node-config-provider': 3.1.3 - '@smithy/node-http-handler': 3.1.1 - '@smithy/protocol-http': 4.0.3 - '@smithy/smithy-client': 3.1.5 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 '@smithy/types': 3.3.0 '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.7 - '@smithy/util-defaults-mode-node': 3.0.7 - '@smithy/util-endpoints': 2.0.4 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 '@smithy/util-middleware': 3.0.3 '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 @@ -2482,28 +2522,28 @@ packages: '@aws-sdk/util-endpoints': 3.609.0 '@aws-sdk/util-user-agent-browser': 3.609.0 '@aws-sdk/util-user-agent-node': 3.609.0 - '@smithy/config-resolver': 3.0.4 - '@smithy/core': 2.2.4 - '@smithy/fetch-http-handler': 3.2.0 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 '@smithy/hash-node': 3.0.3 '@smithy/invalid-dependency': 3.0.3 - '@smithy/middleware-content-length': 3.0.3 - '@smithy/middleware-endpoint': 3.0.4 - '@smithy/middleware-retry': 3.0.7 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 '@smithy/middleware-serde': 3.0.3 '@smithy/middleware-stack': 3.0.3 - '@smithy/node-config-provider': 3.1.3 - '@smithy/node-http-handler': 3.1.1 - '@smithy/protocol-http': 4.0.3 - '@smithy/smithy-client': 3.1.5 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 '@smithy/types': 3.3.0 '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.7 - '@smithy/util-defaults-mode-node': 3.0.7 - '@smithy/util-endpoints': 2.0.4 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 '@smithy/util-middleware': 3.0.3 '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 @@ -2628,28 +2668,28 @@ packages: '@aws-sdk/util-endpoints': 3.598.0 '@aws-sdk/util-user-agent-browser': 3.598.0 '@aws-sdk/util-user-agent-node': 3.598.0 - '@smithy/config-resolver': 3.0.4 - '@smithy/core': 2.2.4 - '@smithy/fetch-http-handler': 3.2.0 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 '@smithy/hash-node': 3.0.3 '@smithy/invalid-dependency': 3.0.3 - '@smithy/middleware-content-length': 3.0.3 - '@smithy/middleware-endpoint': 3.0.4 - '@smithy/middleware-retry': 3.0.7 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 '@smithy/middleware-serde': 3.0.3 '@smithy/middleware-stack': 3.0.3 - '@smithy/node-config-provider': 3.1.3 - '@smithy/node-http-handler': 3.1.1 - '@smithy/protocol-http': 4.0.3 - '@smithy/smithy-client': 3.1.5 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 '@smithy/types': 3.3.0 '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.7 - '@smithy/util-defaults-mode-node': 3.0.7 - '@smithy/util-endpoints': 2.0.4 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 '@smithy/util-middleware': 3.0.3 '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 @@ -2674,28 +2714,28 @@ packages: '@aws-sdk/util-endpoints': 3.609.0 '@aws-sdk/util-user-agent-browser': 3.609.0 '@aws-sdk/util-user-agent-node': 3.609.0 - '@smithy/config-resolver': 3.0.4 - '@smithy/core': 2.2.4 - '@smithy/fetch-http-handler': 3.2.0 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 '@smithy/hash-node': 3.0.3 '@smithy/invalid-dependency': 3.0.3 - '@smithy/middleware-content-length': 3.0.3 - '@smithy/middleware-endpoint': 3.0.4 - '@smithy/middleware-retry': 3.0.7 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 '@smithy/middleware-serde': 3.0.3 '@smithy/middleware-stack': 3.0.3 - '@smithy/node-config-provider': 3.1.3 - '@smithy/node-http-handler': 3.1.1 - '@smithy/protocol-http': 4.0.3 - '@smithy/smithy-client': 3.1.5 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 '@smithy/types': 3.3.0 '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.7 - '@smithy/util-defaults-mode-node': 3.0.7 - '@smithy/util-endpoints': 2.0.4 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 '@smithy/util-middleware': 3.0.3 '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 @@ -2768,28 +2808,28 @@ packages: '@aws-sdk/util-endpoints': 3.598.0 '@aws-sdk/util-user-agent-browser': 3.598.0 '@aws-sdk/util-user-agent-node': 3.598.0 - '@smithy/config-resolver': 3.0.4 - '@smithy/core': 2.2.4 - '@smithy/fetch-http-handler': 3.2.0 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 '@smithy/hash-node': 3.0.3 '@smithy/invalid-dependency': 3.0.3 - '@smithy/middleware-content-length': 3.0.3 - '@smithy/middleware-endpoint': 3.0.4 - '@smithy/middleware-retry': 3.0.7 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 '@smithy/middleware-serde': 3.0.3 '@smithy/middleware-stack': 3.0.3 - '@smithy/node-config-provider': 3.1.3 - '@smithy/node-http-handler': 3.1.1 - '@smithy/protocol-http': 4.0.3 - '@smithy/smithy-client': 3.1.5 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 '@smithy/types': 3.3.0 '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.7 - '@smithy/util-defaults-mode-node': 3.0.7 - '@smithy/util-endpoints': 2.0.4 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 '@smithy/util-middleware': 3.0.3 '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 @@ -2898,10 +2938,10 @@ packages: resolution: {integrity: sha512-HaSjt7puO5Cc7cOlrXFCW0rtA0BM9lvzjl56x0A20Pt+0wxXGeTOZZOkXQIepbrFkV2e/HYukuT9e99vXDm59g==} engines: {node: '>=16.0.0'} dependencies: - '@smithy/core': 2.2.4 - '@smithy/protocol-http': 4.0.3 + '@smithy/core': 2.4.0 + '@smithy/protocol-http': 4.1.0 '@smithy/signature-v4': 3.1.2 - '@smithy/smithy-client': 3.1.5 + '@smithy/smithy-client': 3.2.0 '@smithy/types': 3.3.0 fast-xml-parser: 4.2.5 tslib: 2.6.3 @@ -2911,10 +2951,10 @@ packages: resolution: {integrity: sha512-ptqw+DTxLr01+pKjDUuo53SEDzI+7nFM3WfQaEo0yhDg8vWw8PER4sWj1Ysx67ksctnZesPUjqxd5SHbtdBxiA==} engines: {node: '>=16.0.0'} dependencies: - '@smithy/core': 2.2.4 - '@smithy/protocol-http': 4.0.3 + '@smithy/core': 2.4.0 + '@smithy/protocol-http': 4.1.0 '@smithy/signature-v4': 3.1.2 - '@smithy/smithy-client': 3.1.5 + '@smithy/smithy-client': 3.2.0 '@smithy/types': 3.3.0 fast-xml-parser: 4.2.5 tslib: 2.6.3 @@ -2999,13 +3039,13 @@ packages: engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/fetch-http-handler': 3.2.0 - '@smithy/node-http-handler': 3.1.1 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/node-http-handler': 3.1.4 '@smithy/property-provider': 3.1.3 - '@smithy/protocol-http': 4.0.3 - '@smithy/smithy-client': 3.1.5 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 '@smithy/types': 3.3.0 - '@smithy/util-stream': 3.0.5 + '@smithy/util-stream': 3.1.3 tslib: 2.6.3 dev: false @@ -3150,9 +3190,9 @@ packages: '@aws-sdk/credential-provider-sso': 3.598.0(@aws-sdk/client-sso-oidc@3.600.0) '@aws-sdk/credential-provider-web-identity': 3.598.0(@aws-sdk/client-sts@3.600.0) '@aws-sdk/types': 3.598.0 - '@smithy/credential-provider-imds': 3.1.3 + '@smithy/credential-provider-imds': 3.2.0 '@smithy/property-provider': 3.1.3 - '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: @@ -3290,7 +3330,7 @@ packages: '@aws-sdk/token-providers': 3.598.0(@aws-sdk/client-sso-oidc@3.600.0) '@aws-sdk/types': 3.598.0 '@smithy/property-provider': 3.1.3 - '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: @@ -3437,8 +3477,8 @@ packages: dependencies: '@aws-sdk/types': 3.598.0 '@aws-sdk/util-arn-parser': 3.568.0 - '@smithy/node-config-provider': 3.1.3 - '@smithy/protocol-http': 4.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 '@smithy/util-config-provider': 3.0.0 tslib: 2.6.3 @@ -3474,7 +3514,7 @@ packages: '@aws-crypto/crc32c': 5.2.0 '@aws-sdk/types': 3.598.0 '@smithy/is-array-buffer': 3.0.0 - '@smithy/protocol-http': 4.0.3 + '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 @@ -3485,7 +3525,7 @@ packages: engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.598.0 - '@smithy/protocol-http': 4.0.3 + '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 tslib: 2.6.3 dev: false @@ -3495,7 +3535,7 @@ packages: engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/protocol-http': 4.0.3 + '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 tslib: 2.6.3 dev: false @@ -3542,7 +3582,7 @@ packages: engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.598.0 - '@smithy/protocol-http': 4.0.3 + '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 tslib: 2.6.3 dev: false @@ -3552,7 +3592,7 @@ packages: engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/protocol-http': 4.0.3 + '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 tslib: 2.6.3 dev: false @@ -3573,10 +3613,10 @@ packages: dependencies: '@aws-sdk/types': 3.598.0 '@aws-sdk/util-arn-parser': 3.568.0 - '@smithy/node-config-provider': 3.1.3 - '@smithy/protocol-http': 4.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/protocol-http': 4.1.0 '@smithy/signature-v4': 3.1.2 - '@smithy/smithy-client': 3.1.5 + '@smithy/smithy-client': 3.2.0 '@smithy/types': 3.3.0 '@smithy/util-config-provider': 3.0.0 tslib: 2.6.3 @@ -3600,7 +3640,7 @@ packages: dependencies: '@aws-sdk/types': 3.598.0 '@smithy/property-provider': 3.1.3 - '@smithy/protocol-http': 4.0.3 + '@smithy/protocol-http': 4.1.0 '@smithy/signature-v4': 3.1.2 '@smithy/types': 3.3.0 '@smithy/util-middleware': 3.0.3 @@ -3622,7 +3662,7 @@ packages: dependencies: '@aws-sdk/types': 3.598.0 '@aws-sdk/util-endpoints': 3.598.0 - '@smithy/protocol-http': 4.0.3 + '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 tslib: 2.6.3 dev: false @@ -3633,7 +3673,7 @@ packages: dependencies: '@aws-sdk/types': 3.609.0 '@aws-sdk/util-endpoints': 3.609.0 - '@smithy/protocol-http': 4.0.3 + '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 tslib: 2.6.3 dev: false @@ -3654,7 +3694,7 @@ packages: engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.598.0 - '@smithy/node-config-provider': 3.1.3 + '@smithy/node-config-provider': 3.1.4 '@smithy/types': 3.3.0 '@smithy/util-config-provider': 3.0.0 '@smithy/util-middleware': 3.0.3 @@ -3666,7 +3706,7 @@ packages: engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/node-config-provider': 3.1.3 + '@smithy/node-config-provider': 3.1.4 '@smithy/types': 3.3.0 '@smithy/util-config-provider': 3.0.0 '@smithy/util-middleware': 3.0.3 @@ -3691,7 +3731,7 @@ packages: dependencies: '@aws-sdk/middleware-sdk-s3': 3.598.0 '@aws-sdk/types': 3.598.0 - '@smithy/protocol-http': 4.0.3 + '@smithy/protocol-http': 4.1.0 '@smithy/signature-v4': 3.1.2 '@smithy/types': 3.3.0 tslib: 2.6.3 @@ -3706,7 +3746,7 @@ packages: '@aws-sdk/client-sso-oidc': 3.600.0(@aws-sdk/client-sts@3.600.0) '@aws-sdk/types': 3.598.0 '@smithy/property-provider': 3.1.3 - '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 dev: false @@ -3720,7 +3760,7 @@ packages: '@aws-sdk/client-sso-oidc': 3.609.0(@aws-sdk/client-sts@3.609.0) '@aws-sdk/types': 3.609.0 '@smithy/property-provider': 3.1.3 - '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 dev: false @@ -3734,7 +3774,7 @@ packages: '@aws-sdk/client-sso-oidc': 3.637.0(@aws-sdk/client-sts@3.609.0) '@aws-sdk/types': 3.609.0 '@smithy/property-provider': 3.1.3 - '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 dev: false @@ -3792,7 +3832,7 @@ packages: dependencies: '@aws-sdk/types': 3.598.0 '@smithy/types': 3.3.0 - '@smithy/util-endpoints': 2.0.4 + '@smithy/util-endpoints': 2.0.5 tslib: 2.6.3 dev: false @@ -3802,7 +3842,7 @@ packages: dependencies: '@aws-sdk/types': 3.609.0 '@smithy/types': 3.3.0 - '@smithy/util-endpoints': 2.0.4 + '@smithy/util-endpoints': 2.0.5 tslib: 2.6.3 dev: false @@ -3861,7 +3901,7 @@ packages: optional: true dependencies: '@aws-sdk/types': 3.598.0 - '@smithy/node-config-provider': 3.1.3 + '@smithy/node-config-provider': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 dev: false @@ -3876,7 +3916,7 @@ packages: optional: true dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/node-config-provider': 3.1.3 + '@smithy/node-config-provider': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 dev: false @@ -4735,7 +4775,7 @@ packages: resolution: {integrity: sha512-VwiOk7TwXoE7NlNguV/aPq1hFH72tqkHCw8eWXbr2xHspRyyv9DLpLXhq+Ieje+NwoqXrY0xyQjPXdOE6cGcHA==} engines: {node: '>=16.0.0'} dependencies: - '@smithy/node-config-provider': 3.1.3 + '@smithy/node-config-provider': 3.1.4 '@smithy/types': 3.3.0 '@smithy/util-config-provider': 3.0.0 '@smithy/util-middleware': 3.0.3 @@ -4787,7 +4827,7 @@ packages: resolution: {integrity: sha512-U1Yrv6hx/mRK6k8AncuI6jLUx9rn0VVSd9NPEX6pyYFBfkSkChOc/n4zUb8alHUVg83TbI4OdZVo1X0Zfj3ijA==} engines: {node: '>=16.0.0'} dependencies: - '@smithy/node-config-provider': 3.1.3 + '@smithy/node-config-provider': 3.1.4 '@smithy/property-provider': 3.1.3 '@smithy/types': 3.3.0 '@smithy/url-parser': 3.0.3 @@ -4949,7 +4989,7 @@ packages: engines: {node: '>=16.0.0'} dependencies: '@smithy/middleware-serde': 3.0.3 - '@smithy/node-config-provider': 3.1.3 + '@smithy/node-config-provider': 3.1.4 '@smithy/shared-ini-file-loader': 3.1.3 '@smithy/types': 3.3.0 '@smithy/url-parser': 3.0.3 @@ -4989,10 +5029,10 @@ packages: resolution: {integrity: sha512-f5q7Y09G+2h5ivkSx5CHvlAT4qRR3jBFEsfXyQ9nFNiWQlr8c48blnu5cmbTQ+p1xmIO14UXzKoF8d7Tm0Gsjw==} engines: {node: '>=16.0.0'} dependencies: - '@smithy/node-config-provider': 3.1.3 - '@smithy/protocol-http': 4.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/protocol-http': 4.1.0 '@smithy/service-error-classification': 3.0.3 - '@smithy/smithy-client': 3.1.5 + '@smithy/smithy-client': 3.2.0 '@smithy/types': 3.3.0 '@smithy/util-middleware': 3.0.3 '@smithy/util-retry': 3.0.3 @@ -5041,7 +5081,7 @@ packages: engines: {node: '>=16.0.0'} dependencies: '@smithy/abort-controller': 3.1.1 - '@smithy/protocol-http': 4.0.3 + '@smithy/protocol-http': 4.1.0 '@smithy/querystring-builder': 3.0.3 '@smithy/types': 3.3.0 tslib: 2.6.3 @@ -5269,7 +5309,7 @@ packages: engines: {node: '>= 10.0.0'} dependencies: '@smithy/property-provider': 3.1.3 - '@smithy/smithy-client': 3.1.5 + '@smithy/smithy-client': 3.2.0 '@smithy/types': 3.3.0 bowser: 2.11.0 tslib: 2.6.3 @@ -5292,11 +5332,11 @@ packages: resolution: {integrity: sha512-F4Qcj1fG6MGi2BSWCslfsMSwllws/WzYONBGtLybyY+halAcXdWhcew+mej8M5SKd5hqPYp4f7b+ABQEaeytgg==} engines: {node: '>= 10.0.0'} dependencies: - '@smithy/config-resolver': 3.0.4 + '@smithy/config-resolver': 3.0.5 '@smithy/credential-provider-imds': 3.1.3 - '@smithy/node-config-provider': 3.1.3 + '@smithy/node-config-provider': 3.1.4 '@smithy/property-provider': 3.1.3 - '@smithy/smithy-client': 3.1.5 + '@smithy/smithy-client': 3.2.0 '@smithy/types': 3.3.0 tslib: 2.6.3 dev: false From a7a8efe7316f61c2131bb357af63c9d7e21e5ceb Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Fri, 6 Sep 2024 11:35:50 +0200 Subject: [PATCH 133/241] Resolve PR comments --- packages/catalog-platformstate-writer/.env | 2 +- packages/catalog-platformstate-writer/LICENSE | 201 ------------------ .../catalog-platformstate-writer/README.md | 11 - .../catalog-platformstate-writer/src/utils.ts | 14 -- .../dynamoDB-keys.ts | 6 +- .../platform-states-entry.ts | 18 -- .../token-generation-states-entry.ts | 20 +- 7 files changed, 14 insertions(+), 258 deletions(-) delete mode 100644 packages/catalog-platformstate-writer/LICENSE delete mode 100644 packages/catalog-platformstate-writer/README.md diff --git a/packages/catalog-platformstate-writer/.env b/packages/catalog-platformstate-writer/.env index 861e5be3cc..223c09deae 100644 --- a/packages/catalog-platformstate-writer/.env +++ b/packages/catalog-platformstate-writer/.env @@ -1,7 +1,7 @@ LOG_LEVEL=info KAFKA_CLIENT_ID="catalog" -KAFKA_GROUP_ID="catalog-group" +KAFKA_GROUP_ID="catalog-group-local" KAFKA_BROKERS="localhost:9092" KAFKA_DISABLE_AWS_IAM_AUTH="true" CATALOG_TOPIC="event-store.catalog.events" diff --git a/packages/catalog-platformstate-writer/LICENSE b/packages/catalog-platformstate-writer/LICENSE deleted file mode 100644 index 261eeb9e9f..0000000000 --- a/packages/catalog-platformstate-writer/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/catalog-platformstate-writer/README.md b/packages/catalog-platformstate-writer/README.md deleted file mode 100644 index f2c7b5dc44..0000000000 --- a/packages/catalog-platformstate-writer/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# pagopa-interop-be-event-consumer-poc - -Kafka consumer that consumes Debezium events and updates the read model - -Node version required >=node16 - -Run consumer - -``` -pnpm start -``` diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index 22f3d4c27f..be07a485bd 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -11,9 +11,6 @@ import { GetItemInput, PutItemCommand, PutItemInput, - ScanCommand, - ScanCommandOutput, - ScanInput, } from "@aws-sdk/client-dynamodb"; import { unmarshall } from "@aws-sdk/util-dynamodb"; import { config } from "./config/config.js"; @@ -89,14 +86,3 @@ export const deleteCatalogEntry = async ( const command = new DeleteItemCommand(input); await dynamoDBClient.send(command); }; - -export const readAllItems = async ( - dynamoDBClient: DynamoDBClient -): Promise => { - const readInput: ScanInput = { - TableName: config.tokenGenerationReadModelTableNamePlatform, - }; - const commandQuery = new ScanCommand(readInput); - const read: ScanCommandOutput = await dynamoDBClient.send(commandQuery); - return read; -}; diff --git a/packages/models/src/token-generation-readmodel/dynamoDB-keys.ts b/packages/models/src/token-generation-readmodel/dynamoDB-keys.ts index 292e4b6ebc..201a1829bd 100644 --- a/packages/models/src/token-generation-readmodel/dynamoDB-keys.ts +++ b/packages/models/src/token-generation-readmodel/dynamoDB-keys.ts @@ -63,7 +63,7 @@ export const makeGSIPKConsumerIdEServiceId = ({ export const TokenGenerationStatesClientKidPurposePK = z .string() - .brand(`ESERVICEDESCRIPTOR#eServiceId#descriptorId`); + .brand(`CLIENTKIDPURPOSE#clientId#kid#purposeId`); export type TokenGenerationStatesClientKidPurposePK = z.infer< typeof TokenGenerationStatesClientKidPurposePK >; @@ -80,7 +80,7 @@ export const makeTokenGenerationStatesClientKidPurposePK = ({ export const TokenGenerationStatesClientKidPK = z .string() - .brand(`ESERVICEDESCRIPTOR#eServiceId#descriptorId`); + .brand(`CLIENTKID#clientId#kid`); export type TokenGenerationStatesClientKidPK = z.infer< typeof TokenGenerationStatesClientKidPK >; @@ -91,7 +91,7 @@ export const makeTokenGenerationStatesClientKidPK = ({ clientId: ClientId; kid: string; }): TokenGenerationStatesClientKidPK => - `CLIENTKIDPURPOSE#${clientId}#${kid}` as TokenGenerationStatesClientKidPK; + `CLIENTKID#${clientId}#${kid}` as TokenGenerationStatesClientKidPK; export const GSIPKEServiceIdDescriptorId = z .string() diff --git a/packages/models/src/token-generation-readmodel/platform-states-entry.ts b/packages/models/src/token-generation-readmodel/platform-states-entry.ts index ed80bb0886..52bc81ba1a 100644 --- a/packages/models/src/token-generation-readmodel/platform-states-entry.ts +++ b/packages/models/src/token-generation-readmodel/platform-states-entry.ts @@ -23,24 +23,6 @@ export const ItemState = z.enum([ ]); export type ItemState = z.infer; -/* -export const PlatformStatesEServiceDescriptorPK = z.literal( - `ESERVICEDESCRIPTOR#${EServiceId}#${DescriptorId}` -); -export type PlatformStatesEServiceDescriptorPK = z.infer< - typeof PlatformStatesEServiceDescriptorPK ->; - -const a: PlatformStatesEServiceDescriptorPK = `ESERVICEDESCRIPTOR#${generateId()}#${generateId()}`; // OK -const b: PlatformStatesEServiceDescriptorPK = `ESERVICEDESCRIPTOR#${generateId()}#${generateId()}`; // OK -const c: PlatformStatesEServiceDescriptorPK = `ESERVICEDESCRIPTOR#test#test`; // OK -const d: PlatformStatesEServiceDescriptorPK = `ESERVICEDESCRIPTOR#test#`; // OK -const e: PlatformStatesEServiceDescriptorPK = `ESERVICEDESCRIPTOR#test`; // WRONG -const f: PlatformStatesEServiceDescriptorPK = `test#test#test`; // WRONG - -We don't check the structure of the ids because they are treated as strings -*/ - const PlatformStatesBaseEntry = z.object({ state: ItemState, version: z.number(), diff --git a/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts b/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts index e35c5f5045..fa9a9b755e 100644 --- a/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts +++ b/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts @@ -22,7 +22,6 @@ const TokenGenerationStatesBaseEntry = z.object({ publicKey: z.string(), GSIPK_clientId: ClientId, GSIPK_kid: z.string(), - GSIPK_clientId_purposeId: GSIPKClientIdPurposeId, updatedAt: z.string().datetime(), }); type TokenGenerationStatesBaseEntry = z.infer< @@ -32,15 +31,16 @@ type TokenGenerationStatesBaseEntry = z.infer< export const TokenGenerationStatesClientPurposeEntry = TokenGenerationStatesBaseEntry.extend({ PK: TokenGenerationStatesClientKidPurposePK, - GSIPK_consumerId_eserviceId: GSIPKConsumerIdEServiceId, - agreementId: AgreementId, - agreementState: ItemState, - GSIPK_eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, - descriptorState: ItemState, - descriptorAudience: z.string(), - GSIPK_purposeId: PurposeId, - purposeState: ItemState, - purposeVersionId: PurposeVersionId, + GSIPK_consumerId_eserviceId: GSIPKConsumerIdEServiceId.optional(), + agreementId: AgreementId.optional(), + agreementState: ItemState.optional(), + GSIPK_eserviceId_descriptorId: GSIPKEServiceIdDescriptorId.optional(), + descriptorState: ItemState.optional(), + descriptorAudience: z.string().optional(), + GSIPK_purposeId: PurposeId.optional(), + purposeState: ItemState.optional(), + purposeVersionId: PurposeVersionId.optional(), + GSIPK_clientId_purposeId: GSIPKClientIdPurposeId.optional(), }); export type TokenGenerationStatesClientPurposeEntry = z.infer< typeof TokenGenerationStatesClientPurposeEntry From 7a17057e2776e55379b3141ed8494cf674d521fd Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 6 Sep 2024 11:59:59 +0200 Subject: [PATCH 134/241] Fix --- .../src/consumerServiceV2.ts | 10 +- .../catalog-platformstate-writer/src/utils.ts | 106 +--------------- ...logPlatformstateWriter.integration.test.ts | 47 ++----- .../test/utils.ts | 118 ++++++++++++++++++ 4 files changed, 135 insertions(+), 146 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index be93ec5e30..3225a6289a 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -38,7 +38,7 @@ export async function handleMessageV2( ); // flow for current descriptor - { + const processCurrentDescriptor = async (): Promise => { const primaryKeyCurrent = makePlatformStatesEServiceDescriptorPK({ eserviceId: eservice.id, descriptorId: descriptor.id, @@ -52,7 +52,7 @@ export async function handleMessageV2( existingCatalogEntryCurrent.version > msg.version ) { // Stops processing if the message is older than the catalog entry - return; + return Promise.resolve(); } else if ( existingCatalogEntryCurrent && existingCatalogEntryCurrent.version <= msg.version @@ -96,7 +96,9 @@ export async function handleMessageV2( dynamoDBClient ); } - } + }; + + await processCurrentDescriptor(); // flow for previous descriptor @@ -108,7 +110,7 @@ export async function handleMessageV2( } else { const primaryKeyPrevious = makePlatformStatesEServiceDescriptorPK({ eserviceId: eservice.id, - descriptorId: descriptor.id, + descriptorId: previousDescriptor.id, }); await deleteCatalogEntry(primaryKeyPrevious, dynamoDBClient); diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index 0f591470b6..ae3dfb049e 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -152,113 +152,12 @@ export const updateDescriptorStateInPlatformStatesEntry = async ( UpdateExpression: "SET #state = :newState, version = :newVersion, updatedAt = :newUpdateAt", TableName: config.tokenGenerationReadModelTableNamePlatform, - ReturnValues: "ALL_NEW", + ReturnValues: "NONE", }; const command = new UpdateItemCommand(input); await dynamoDBClient.send(command); }; -export const writeTokenStateEntry = async ( - tokenStateEntry: TokenGenerationStatesClientPurposeEntry, - dynamoDBClient: DynamoDBClient -): Promise => { - const input: PutItemInput = { - ConditionExpression: "attribute_not_exists(PK)", - Item: { - PK: { - S: tokenStateEntry.PK, - }, - descriptorState: { - S: tokenStateEntry.descriptorState, - }, - descriptorAudience: { - S: tokenStateEntry.descriptorAudience, - }, - updatedAt: { - S: tokenStateEntry.updatedAt, - }, - consumerId: { - S: tokenStateEntry.consumerId, - }, - agreementId: { - S: tokenStateEntry.agreementId, - }, - purposeVersionId: { - S: tokenStateEntry.purposeVersionId, - }, - GSIPK_consumerId_eserviceId: { - S: tokenStateEntry.GSIPK_consumerId_eserviceId, - }, - clientKind: { - S: tokenStateEntry.clientKind, - }, - publicKey: { - S: tokenStateEntry.publicKey, - }, - GSIPK_clientId: { - S: tokenStateEntry.GSIPK_clientId, - }, - GSIPK_kid: { - S: tokenStateEntry.GSIPK_kid, - }, - GSIPK_clientId_purposeId: { - S: tokenStateEntry.GSIPK_clientId_purposeId, - }, - agreementState: { - S: tokenStateEntry.agreementState, - }, - GSIPK_eserviceId_descriptorId: { - S: tokenStateEntry.GSIPK_eserviceId_descriptorId, - }, - GSIPK_purposeId: { - S: tokenStateEntry.GSIPK_purposeId, - }, - purposeState: { - S: tokenStateEntry.purposeState, - }, - }, - TableName: config.tokenGenerationReadModelTableNameTokenGeneration, - }; - const command = new PutItemCommand(input); - await dynamoDBClient.send(command); -}; - -/* -export const readTokenStateEntryByEServiceIdAndDescriptorId = async ( - eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, - dynamoDBClient: DynamoDBClient -): Promise => { - const input: QueryInput = { - TableName: config.tokenGenerationReadModelTableNameTokenGeneration, - IndexName: "GSIPK_eserviceId_descriptorId", // Use the name of your Global Secondary Index - KeyConditionExpression: `GSIPK_eserviceId_descriptorId = :gsiValue`, - ExpressionAttributeValues: { - ":gsiValue": { S: eserviceId_descriptorId }, - }, - ScanIndexForward: false, - }; - const command = new QueryCommand(input); - const data: QueryCommandOutput = await dynamoDBClient.send(command); - - if (!data.Items) { - return undefined; - } else { - const unmarshalled = unmarshall(data.Items[0]); - const tokenStateEntry = - TokenGenerationStatesClientPurposeEntry.safeParse(unmarshalled); - - if (!tokenStateEntry.success) { - throw genericInternalError( - `Unable to parse token state entry item: result ${JSON.stringify( - tokenStateEntry - )} - data ${JSON.stringify(data)} ` - ); - } - return tokenStateEntry.data; - } -}; -*/ - export const readTokenStateEntriesByEserviceIdAndDescriptorId = async ( eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, dynamoDBClient: DynamoDBClient @@ -270,7 +169,6 @@ export const readTokenStateEntriesByEserviceIdAndDescriptorId = async ( ExpressionAttributeValues: { ":gsi_value": { S: eserviceId_descriptorId }, }, - ScanIndexForward: false, }; const command = new QueryCommand(input); const data: QueryCommandOutput = await dynamoDBClient.send(command); @@ -327,7 +225,7 @@ export const updateDescriptorStateInTokenGenerationStatesTable = async ( UpdateExpression: "SET descriptorState = :newState, updatedAt = :newUpdateAt", TableName: config.tokenGenerationReadModelTableNameTokenGeneration, - ReturnValues: "ALL_NEW", + ReturnValues: "NONE", }; const command = new UpdateItemCommand(input); await dynamoDBClient.send(command); diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 3a6e29e534..d4e8867773 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -56,10 +56,14 @@ import { updateDescriptorStateInPlatformStatesEntry, updateDescriptorStateInTokenGenerationStatesTable, writeCatalogEntry, - writeTokenStateEntry, } from "../src/utils.js"; import { handleMessageV2 } from "../src/consumerServiceV2.js"; -import { config, sleep } from "./utils.js"; +import { + config, + readAllTokenStateItems, + sleep, + writeTokenStateEntry, +} from "./utils.js"; describe("integration tests", async () => { if (!config) { @@ -1095,13 +1099,15 @@ describe("integration tests", async () => { const primaryKey = makePlatformStatesEServiceDescriptorPK({ eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, + descriptorId: archivedDescriptor.id, }); const retrievedEntry = await readCatalogEntry( primaryKey, dynamoDBClient ); expect(retrievedEntry).toBeUndefined(); + + // TO DO token-generation-states }); }); @@ -1320,38 +1326,3 @@ describe("integration tests", async () => { }); }); }); - -const readAllTokenStateItems = async ( - dynamoDBClient: DynamoDBClient -): Promise => { - if (!config) { - fail(); - } - - const readInput: ScanInput = { - TableName: config.tokenGenerationReadModelTableNameTokenGeneration, - }; - const commandQuery = new ScanCommand(readInput); - const data: ScanCommandOutput = await dynamoDBClient.send(commandQuery); - - if (!data.Items) { - throw genericInternalError( - `Unable to read token state entries: result ${JSON.stringify(data)} ` - ); - } else { - const unmarshalledItems = data.Items.map((item) => unmarshall(item)); - - const tokenStateEntries = z - .array(TokenGenerationStatesClientPurposeEntry) - .safeParse(unmarshalledItems); - - if (!tokenStateEntries.success) { - throw genericInternalError( - `Unable to parse token state entry item: result ${JSON.stringify( - tokenStateEntries - )} - data ${JSON.stringify(data)} ` - ); - } - return tokenStateEntries.data; - } -}; diff --git a/packages/catalog-platformstate-writer/test/utils.ts b/packages/catalog-platformstate-writer/test/utils.ts index 0605faf7d8..c0dc8ca47e 100644 --- a/packages/catalog-platformstate-writer/test/utils.ts +++ b/packages/catalog-platformstate-writer/test/utils.ts @@ -1,5 +1,20 @@ +import { fail } from "assert"; +import { + DynamoDBClient, + PutItemCommand, + PutItemInput, + ScanCommand, + ScanCommandOutput, + ScanInput, +} from "@aws-sdk/client-dynamodb"; import { setupTestContainersVitest } from "pagopa-interop-commons-test/index.js"; +import { + genericInternalError, + TokenGenerationStatesClientPurposeEntry, +} from "pagopa-interop-models"; import { afterEach, inject, vi } from "vitest"; +import { unmarshall } from "@aws-sdk/util-dynamodb"; +import { z } from "zod"; export const config = inject("tokenGenerationReadModelConfig"); export const { cleanup } = setupTestContainersVitest(); @@ -13,3 +28,106 @@ export const sleep = (ms: number, mockDate = new Date()): Promise => vi.useFakeTimers(); vi.setSystemTime(mockDate); }); + +export const writeTokenStateEntry = async ( + tokenStateEntry: TokenGenerationStatesClientPurposeEntry, + dynamoDBClient: DynamoDBClient +): Promise => { + if (!config) { + fail(); + } + const input: PutItemInput = { + ConditionExpression: "attribute_not_exists(PK)", + Item: { + PK: { + S: tokenStateEntry.PK, + }, + descriptorState: { + S: tokenStateEntry.descriptorState, + }, + descriptorAudience: { + S: tokenStateEntry.descriptorAudience, + }, + updatedAt: { + S: tokenStateEntry.updatedAt, + }, + consumerId: { + S: tokenStateEntry.consumerId, + }, + agreementId: { + S: tokenStateEntry.agreementId, + }, + purposeVersionId: { + S: tokenStateEntry.purposeVersionId, + }, + GSIPK_consumerId_eserviceId: { + S: tokenStateEntry.GSIPK_consumerId_eserviceId, + }, + clientKind: { + S: tokenStateEntry.clientKind, + }, + publicKey: { + S: tokenStateEntry.publicKey, + }, + GSIPK_clientId: { + S: tokenStateEntry.GSIPK_clientId, + }, + GSIPK_kid: { + S: tokenStateEntry.GSIPK_kid, + }, + GSIPK_clientId_purposeId: { + S: tokenStateEntry.GSIPK_clientId_purposeId, + }, + agreementState: { + S: tokenStateEntry.agreementState, + }, + GSIPK_eserviceId_descriptorId: { + S: tokenStateEntry.GSIPK_eserviceId_descriptorId, + }, + GSIPK_purposeId: { + S: tokenStateEntry.GSIPK_purposeId, + }, + purposeState: { + S: tokenStateEntry.purposeState, + }, + }, + TableName: config.tokenGenerationReadModelTableNameTokenGeneration, + }; + const command = new PutItemCommand(input); + await dynamoDBClient.send(command); +}; + +export const readAllTokenStateItems = async ( + dynamoDBClient: DynamoDBClient +): Promise => { + if (!config) { + fail(); + } + + const readInput: ScanInput = { + TableName: config.tokenGenerationReadModelTableNameTokenGeneration, + }; + const commandQuery = new ScanCommand(readInput); + const data: ScanCommandOutput = await dynamoDBClient.send(commandQuery); + + if (!data.Items) { + throw genericInternalError( + `Unable to read token state entries: result ${JSON.stringify(data)} ` + ); + } else { + const unmarshalledItems = data.Items.map((item) => unmarshall(item)); + + const tokenStateEntries = z + .array(TokenGenerationStatesClientPurposeEntry) + .safeParse(unmarshalledItems); + + if (!tokenStateEntries.success) { + throw genericInternalError( + `Unable to parse token state entry item: result ${JSON.stringify( + tokenStateEntries + )} - data ${JSON.stringify(data)} ` + ); + } + return tokenStateEntries.data; + } +}; From 1e6d50ab453d3ca3b33b92f3cdfdefd118ec1ae9 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 6 Sep 2024 12:10:55 +0200 Subject: [PATCH 135/241] Remove comment --- packages/catalog-platformstate-writer/src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index 847b4d4563..8334bcc325 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -151,7 +151,7 @@ export const readTokenStateEntriesByEserviceIdAndDescriptorId = async ( ): Promise => { const input: QueryInput = { TableName: config.tokenGenerationReadModelTableNameTokenGeneration, - IndexName: "GSIPK_eserviceId_descriptorId", // Use the name of your Global Secondary Index + IndexName: "GSIPK_eserviceId_descriptorId", KeyConditionExpression: `GSIPK_eserviceId_descriptorId = :gsi_value`, ExpressionAttributeValues: { ":gsi_value": { S: eserviceId_descriptorId }, From f891ff15011de83240448b67d68051b0b986372e Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 6 Sep 2024 12:13:08 +0200 Subject: [PATCH 136/241] Renaming --- packages/catalog-platformstate-writer/src/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index 8334bcc325..fdbac5532b 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -152,9 +152,9 @@ export const readTokenStateEntriesByEserviceIdAndDescriptorId = async ( const input: QueryInput = { TableName: config.tokenGenerationReadModelTableNameTokenGeneration, IndexName: "GSIPK_eserviceId_descriptorId", - KeyConditionExpression: `GSIPK_eserviceId_descriptorId = :gsi_value`, + KeyConditionExpression: `GSIPK_eserviceId_descriptorId = :gsiValue`, ExpressionAttributeValues: { - ":gsi_value": { S: eserviceId_descriptorId }, + ":gsiValue": { S: eserviceId_descriptorId }, }, }; const command = new QueryCommand(input); From b0466037b58103754813778c0915e6d0464fbb1c Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 6 Sep 2024 12:26:06 +0200 Subject: [PATCH 137/241] Fix --- ...logPlatformstateWriter.integration.test.ts | 6 -- .../test/utils.ts | 22 +++--- packages/commons-test/src/testUtils.ts | 72 ++++++++++--------- 3 files changed, 51 insertions(+), 49 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index d4e8867773..16aa3e938a 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -22,7 +22,6 @@ import { TokenGenerationStatesClientPurposeEntry, descriptorState, generateId, - genericInternalError, itemState, makeGSIPKEServiceIdDescriptorId, makePlatformStatesEServiceDescriptorPK, @@ -36,9 +35,6 @@ import { DeleteTableCommand, DeleteTableInput, DynamoDBClient, - ScanCommand, - ScanCommandOutput, - ScanInput, } from "@aws-sdk/client-dynamodb"; import { getMockDescriptor, @@ -46,8 +42,6 @@ import { getMockDocument, getMockTokenStatesClientPurposeEntry, } from "pagopa-interop-commons-test"; -import { unmarshall } from "@aws-sdk/util-dynamodb"; -import { z } from "zod"; import { deleteCatalogEntry, descriptorStateToClientState, diff --git a/packages/catalog-platformstate-writer/test/utils.ts b/packages/catalog-platformstate-writer/test/utils.ts index c0dc8ca47e..36b0274a93 100644 --- a/packages/catalog-platformstate-writer/test/utils.ts +++ b/packages/catalog-platformstate-writer/test/utils.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ import { fail } from "assert"; import { DynamoDBClient, @@ -43,10 +45,10 @@ export const writeTokenStateEntry = async ( S: tokenStateEntry.PK, }, descriptorState: { - S: tokenStateEntry.descriptorState, + S: tokenStateEntry.descriptorState!, }, descriptorAudience: { - S: tokenStateEntry.descriptorAudience, + S: tokenStateEntry.descriptorAudience!, }, updatedAt: { S: tokenStateEntry.updatedAt, @@ -55,13 +57,13 @@ export const writeTokenStateEntry = async ( S: tokenStateEntry.consumerId, }, agreementId: { - S: tokenStateEntry.agreementId, + S: tokenStateEntry.agreementId!, }, purposeVersionId: { - S: tokenStateEntry.purposeVersionId, + S: tokenStateEntry.purposeVersionId!, }, GSIPK_consumerId_eserviceId: { - S: tokenStateEntry.GSIPK_consumerId_eserviceId, + S: tokenStateEntry.GSIPK_consumerId_eserviceId!, }, clientKind: { S: tokenStateEntry.clientKind, @@ -76,19 +78,19 @@ export const writeTokenStateEntry = async ( S: tokenStateEntry.GSIPK_kid, }, GSIPK_clientId_purposeId: { - S: tokenStateEntry.GSIPK_clientId_purposeId, + S: tokenStateEntry.GSIPK_clientId_purposeId!, }, agreementState: { - S: tokenStateEntry.agreementState, + S: tokenStateEntry.agreementState!, }, GSIPK_eserviceId_descriptorId: { - S: tokenStateEntry.GSIPK_eserviceId_descriptorId, + S: tokenStateEntry.GSIPK_eserviceId_descriptorId!, }, GSIPK_purposeId: { - S: tokenStateEntry.GSIPK_purposeId, + S: tokenStateEntry.GSIPK_purposeId!, }, purposeState: { - S: tokenStateEntry.purposeState, + S: tokenStateEntry.purposeState!, }, }, TableName: config.tokenGenerationReadModelTableNameTokenGeneration, diff --git a/packages/commons-test/src/testUtils.ts b/packages/commons-test/src/testUtils.ts index 43bb8effd5..21cfe8c52f 100644 --- a/packages/commons-test/src/testUtils.ts +++ b/packages/commons-test/src/testUtils.ts @@ -43,6 +43,8 @@ import { makeGSIPKEServiceIdDescriptorId, TokenGenerationStatesClientKidPurposePK, makeTokenGenerationStatesClientKidPurposePK, + AgreementId, + PurposeVersionId, } from "pagopa-interop-models"; import { AuthData } from "pagopa-interop-commons"; import { z } from "zod"; @@ -298,37 +300,41 @@ export const getMockAuthData = (organizationId?: TenantId): AuthData => ({ export const getMockTokenStatesClientPurposeEntry = ( tokenStateEntryPK?: TokenGenerationStatesClientKidPurposePK -): TokenGenerationStatesClientPurposeEntry => ({ - PK: - tokenStateEntryPK || - makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), +): TokenGenerationStatesClientPurposeEntry => { + const clientId = generateId(); + const purposeId = generateId(); + return { + PK: + tokenStateEntryPK || + makeTokenGenerationStatesClientKidPurposePK({ + clientId, + kid: `kid ${Math.random()}`, + purposeId, + }), + descriptorState: itemState.inactive, + descriptorAudience: "pagopa.it", + updatedAt: new Date().toISOString(), + consumerId: generateId(), + agreementId: generateId(), + purposeVersionId: generateId(), + GSIPK_consumerId_eserviceId: makeGSIPKConsumerIdEServiceId({ + consumerId: generateId(), + eserviceId: generateId(), }), - descriptorState: itemState.inactive, - descriptorAudience: "pagopa.it", - updatedAt: new Date().toISOString(), - consumerId: generateId(), - agreementId: generateId(), - purposeVersionId: generateId(), - GSIPK_consumerId_eserviceId: makeGSIPKConsumerIdEServiceId({ - consumerId: generateId(), - eserviceId: generateId(), - }), - clientKind: clientKind.consumer, - publicKey: "PEM", - GSIPK_clientId: generateId(), - GSIPK_kid: "KID", - GSIPK_clientId_purposeId: makeGSIPKClientIdPurposeId({ - clientId: generateId(), - purposeId: generateId(), - }), - agreementState: "ACTIVE", - GSIPK_eserviceId_descriptorId: makeGSIPKEServiceIdDescriptorId({ - eserviceId: generateId(), - descriptorId: generateId(), - }), - GSIPK_purposeId: generateId(), - purposeState: itemState.inactive, -}); + clientKind: clientKind.consumer, + publicKey: "PEM", + GSIPK_clientId: clientId, + GSIPK_kid: "KID", + agreementState: "ACTIVE", + GSIPK_eserviceId_descriptorId: makeGSIPKEServiceIdDescriptorId({ + eserviceId: generateId(), + descriptorId: generateId(), + }), + GSIPK_purposeId: purposeId, + purposeState: itemState.inactive, + GSIPK_clientId_purposeId: makeGSIPKClientIdPurposeId({ + clientId, + purposeId, + }), + }; +}; From f7c21e1d57340f8d0c615351eb94b8976523f451 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 6 Sep 2024 14:28:20 +0200 Subject: [PATCH 138/241] Fix logic --- .../src/consumerServiceV2.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index 3225a6289a..b802fae492 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -142,16 +142,9 @@ export async function handleMessageV2( }); const catalogEntry = await readCatalogEntry(primaryKey, dynamoDBClient); - if (!catalogEntry) { - throw genericInternalError( - `Unable to find catalog entry with PK ${primaryKey}` - ); + if (!catalogEntry || catalogEntry.version > msg.version) { + return Promise.resolve(); } else { - // Stops processing if the message is older than the catalog entry - if (catalogEntry.version > msg.version) { - return; - } - await updateDescriptorStateInPlatformStatesEntry( dynamoDBClient, primaryKey, From 6d009cf92b47d5f4cd0e0ec9a1a2669cedb2012a Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 6 Sep 2024 14:28:31 +0200 Subject: [PATCH 139/241] Add tests --- ...logPlatformstateWriter.integration.test.ts | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 16aa3e938a..bf32770445 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -746,6 +746,80 @@ describe("integration tests", async () => { ]) ); }); + it("should not throw error if entry doesn't exist", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.published, + publishedAt: new Date(), + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [publishedDescriptor], + }; + + const payload: EServiceDescriptorActivatedV2 = { + eservice: toEServiceV2(eservice), + descriptorId: publishedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 2, + type: "EServiceDescriptorActivated", + event_version: 2, + data: payload, + log_date: new Date(), + }; + const catalogEntryPrimaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + + // token-generation-states + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + + await sleep(1000, mockDate); + await handleMessageV2(message, dynamoDBClient); + + // platform-states + const retrievedCatalogEntry = await readCatalogEntry( + catalogEntryPrimaryKey, + dynamoDBClient + ); + expect(retrievedCatalogEntry).toBeUndefined(); + }); }); it("EServiceDescriptorArchived", async () => { @@ -1317,6 +1391,81 @@ describe("integration tests", async () => { ]) ); }); + it("should not throw error if entry doesn't exist", async () => { + const suspendedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.suspended, + publishedAt: new Date(), + suspendedAt: new Date(), + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [suspendedDescriptor], + }; + + const payload: EServiceDescriptorActivatedV2 = { + eservice: toEServiceV2(eservice), + descriptorId: suspendedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 2, + type: "EServiceDescriptorActivated", + event_version: 2, + data: payload, + log_date: new Date(), + }; + const catalogEntryPrimaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: suspendedDescriptor.id, + }); + + // token-generation-states + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: suspendedDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: suspendedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: suspendedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + + await sleep(1000, mockDate); + await handleMessageV2(message, dynamoDBClient); + + // platform-states + const retrievedCatalogEntry = await readCatalogEntry( + catalogEntryPrimaryKey, + dynamoDBClient + ); + expect(retrievedCatalogEntry).toBeUndefined(); + }); }); }); }); From 5312b348d8a7720bdeced32e61cfd7dfca0e5547 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 6 Sep 2024 15:26:21 +0200 Subject: [PATCH 140/241] Draft --- .../client-assertion-validation/src/types.ts | 6 +-- .../client-assertion-validation/src/utils.ts | 53 +++++++++++++------ 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/packages/client-assertion-validation/src/types.ts b/packages/client-assertion-validation/src/types.ts index f6eba0ec1c..c0ce589752 100644 --- a/packages/client-assertion-validation/src/types.ts +++ b/packages/client-assertion-validation/src/types.ts @@ -11,7 +11,7 @@ import { z } from "zod"; export const ClientAssertionHeader = z.object({ kid: z.string(), - alg: z.string(), // TODO Enum + alg: z.string(), // TODO Enum, which values? }); export type ClientAssertionHeader = z.infer; @@ -35,9 +35,9 @@ export type ClientAssertion = z.infer; export const Key = z.object({ GSIPK_clientId: ClientId, consumerId: TenantId, - kidWithPurposeId: z.string(), // TO DO which field of the table is mapper to this? + kidWithPurposeId: z.string(), // TO DO which field of the table is mapped to this? publicKey: z.string().min(1), - algorithm: z.literal("RS256"), // no field to map from the table. Is it included extracted from publicKey field? + algorithm: z.literal("RS256"), // no field to map from the table. Is it extracted from publicKey field? }); export type Key = z.infer; diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index a1a66ec648..e0e10d85a8 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -1,15 +1,22 @@ import { authorizationServerApi } from "pagopa-interop-api-clients"; -import { decode, JwtPayload, verify } from "jsonwebtoken"; +import { + decode, + JsonWebTokenError, + JwtPayload, + NotBeforeError, + TokenExpiredError, + verify, +} from "jsonwebtoken"; import { PurposeId } from "pagopa-interop-models"; import { ClientAssertion, ConsumerKey, Key } from "./types.js"; -const CLIENT_ASSERTION_AUDIENCE = ""; +const CLIENT_ASSERTION_AUDIENCE = ""; // To do: env? export const validateRequestParameters = ( request: authorizationServerApi.AccessTokenRequest ): boolean => { const expectedClientAssertionType: string = - "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; - const expectedClientCredentialsGrantType: string = "client_credentials"; + "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; // To do: env? + const expectedClientCredentialsGrantType: string = "client_credentials"; // To do: env? if (request.client_assertion_type !== expectedClientAssertionType) { throw Error( @@ -111,22 +118,36 @@ export const verifyClientAssertionSignature = ( clientAssertionJws: string, key: Key ): JwtPayload => { - const result = verify(clientAssertionJws, b64Decode(key.publicKey), { - algorithms: [key.algorithm], - }); - - // TODO Improve this - if (typeof result === "string") { - throw Error("Unexpected assertion verification result"); - } else { - return result; + try { + const result = verify(clientAssertionJws, b64Decode(key.publicKey), { + algorithms: [key.algorithm], + }); + + // TODO Improve this + if (typeof result === "string") { + throw Error("Unexpected assertion verification result"); + } else { + return result; + } + } catch (error: unknown) { + if (error instanceof TokenExpiredError) { + console.log("TokenExpiredError"); + throw error; + } else if (error instanceof JsonWebTokenError) { + console.log("JsonWebTokenError"); + throw error; + } else if (error instanceof NotBeforeError) { + console.log("NotBeforeError"); + throw error; + } else { + console.log("unknown error"); + throw Error("unknown error"); + } } - // TODO Handle error codes. See https://github.com/auth0/node-jsonwebtoken#errors--codes - - // to do should it be a try/catch? }; export const assertValidPlatformState = (key: ConsumerKey): void => { + // To do: is it ok to have these check throwing errors? So that they can be read if needed (instead of just getting false) if (key.agreementState !== "ACTIVE") { throw Error("Invalid agreement state"); } From 9c9bf27b02e52c7359520e5e35654b86ff32aae1 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 6 Sep 2024 15:56:15 +0200 Subject: [PATCH 141/241] Refactor tables setup --- docker/docker-compose.yml | 16 ++++++++++++-- docker/dynamoDB-tables/platform-states.json | 10 +++++++++ .../token-generation-states.json | 22 +++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 docker/dynamoDB-tables/platform-states.json create mode 100644 docker/dynamoDB-tables/token-generation-states.json diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 2f60dded1a..e6ef2290e6 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -65,20 +65,32 @@ services: - token-generation-readmodel restart: on-failure image: amazon/aws-cli + working_dir: /home/tables + volumes: + - ./dynamoDB-tables:/home/tables environment: AWS_ACCESS_KEY_ID: keyid AWS_SECRET_ACCESS_KEY: key - command: dynamodb create-table --table-name platform-states --attribute-definitions AttributeName=PK,AttributeType=S --key-schema AttributeName=PK,KeyType=HASH --billing-mode PAY_PER_REQUEST --endpoint-url http://token-generation-readmodel:8000 --region eu-central-1 + command: dynamodb create-table + --cli-input-json file://./platform-states.json + --endpoint-url http://token-generation-readmodel:8000 + --region eu-central-1 token-generation-states-table-init: depends_on: - token-generation-readmodel restart: on-failure image: amazon/aws-cli + working_dir: /home/tables + volumes: + - ./dynamoDB-tables:/home/tables environment: AWS_ACCESS_KEY_ID: keyid AWS_SECRET_ACCESS_KEY: key - command: dynamodb create-table --table-name token-generation-states --attribute-definitions AttributeName=PK,AttributeType=S AttributeName=GSIPK_eserviceId_descriptorId,AttributeType=S --key-schema AttributeName=PK,KeyType=HASH --global-secondary-indexes "IndexName=GSIPK_eserviceId_descriptorId,KeySchema=[{AttributeName=GSIPK_eserviceId_descriptorId,KeyType=HASH}],Projection={ProjectionType=ALL}" --billing-mode PAY_PER_REQUEST --endpoint-url http://token-generation-readmodel:8000 --region eu-central-1 + command: dynamodb create-table + --cli-input-json file://./token-generation-states.json + --endpoint-url http://token-generation-readmodel:8000 + --region eu-central-1 dynamodb-admin: image: "aaronshaf/dynamodb-admin" diff --git a/docker/dynamoDB-tables/platform-states.json b/docker/dynamoDB-tables/platform-states.json new file mode 100644 index 0000000000..11d3e645a0 --- /dev/null +++ b/docker/dynamoDB-tables/platform-states.json @@ -0,0 +1,10 @@ +{ + "TableName": "platform-states", + "AttributeDefinitions": [{ "AttributeName": "PK", "AttributeType": "S" }], + "KeySchema": [{ "AttributeName": "PK", "KeyType": "HASH" }], + "ProvisionedThroughput": { + "ReadCapacityUnits": 10, + "WriteCapacityUnits": 5 + }, + "BillingMode": "PAY_PER_REQUEST" +} diff --git a/docker/dynamoDB-tables/token-generation-states.json b/docker/dynamoDB-tables/token-generation-states.json new file mode 100644 index 0000000000..e2b53457ca --- /dev/null +++ b/docker/dynamoDB-tables/token-generation-states.json @@ -0,0 +1,22 @@ +{ + "TableName": "token-generation-states", + "AttributeDefinitions": [ + { "AttributeName": "PK", "AttributeType": "S" }, + { "AttributeName": "GSIPK_eserviceId_descriptorId", "AttributeType": "S" } + ], + "KeySchema": [{ "AttributeName": "PK", "KeyType": "HASH" }], + "GlobalSecondaryIndexes": [ + { + "IndexName": "GSIPK_eserviceId_descriptorId", + "KeySchema": [ + { "AttributeName": "GSIPK_eserviceId_descriptorId", "KeyType": "HASH" } + ], + "Projection": { "ProjectionType": "ALL" } + } + ], + "ProvisionedThroughput": { + "ReadCapacityUnits": 10, + "WriteCapacityUnits": 5 + }, + "BillingMode": "PAY_PER_REQUEST" +} From 6fa48662a4463da8cb34e79eafb68f99efdf114c Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Mon, 9 Sep 2024 10:15:27 +0200 Subject: [PATCH 142/241] Add pagination --- .../catalog-platformstate-writer/src/utils.ts | 81 ++++++++++++------- ...logPlatformstateWriter.integration.test.ts | 42 +++++++++- 2 files changed, 94 insertions(+), 29 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index fdbac5532b..f7da70602b 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import { descriptorState, DescriptorState, @@ -11,6 +10,7 @@ import { TokenGenerationStatesClientPurposeEntry, } from "pagopa-interop-models"; import { + AttributeValue, DeleteItemCommand, DeleteItemInput, DynamoDBClient, @@ -149,37 +149,62 @@ export const readTokenStateEntriesByEserviceIdAndDescriptorId = async ( eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, dynamoDBClient: DynamoDBClient ): Promise => { - const input: QueryInput = { - TableName: config.tokenGenerationReadModelTableNameTokenGeneration, - IndexName: "GSIPK_eserviceId_descriptorId", - KeyConditionExpression: `GSIPK_eserviceId_descriptorId = :gsiValue`, - ExpressionAttributeValues: { - ":gsiValue": { S: eserviceId_descriptorId }, - }, - }; - const command = new QueryCommand(input); - const data: QueryCommandOutput = await dynamoDBClient.send(command); - - if (!data.Items) { - throw genericInternalError( - `Unable to read token state entries: result ${JSON.stringify(data)} ` - ); - } else { - const unmarshalledItems = data.Items.map((item) => unmarshall(item)); - - const tokenStateEntries = z - .array(TokenGenerationStatesClientPurposeEntry) - .safeParse(unmarshalledItems); + const runPaginatedQuery = async ( + eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, + dynamoDBClient: DynamoDBClient, + exclusiveStartKey?: Record + ): Promise => { + const input: QueryInput = { + TableName: config.tokenGenerationReadModelTableNameTokenGeneration, + IndexName: "GSIPK_eserviceId_descriptorId", + KeyConditionExpression: `GSIPK_eserviceId_descriptorId = :gsiValue`, + ExpressionAttributeValues: { + ":gsiValue": { S: eserviceId_descriptorId }, + }, + ExclusiveStartKey: exclusiveStartKey, + }; + const command = new QueryCommand(input); + const data: QueryCommandOutput = await dynamoDBClient.send(command); - if (!tokenStateEntries.success) { + if (!data.Items) { throw genericInternalError( - `Unable to parse token state entry item: result ${JSON.stringify( - tokenStateEntries - )} - data ${JSON.stringify(data)} ` + `Unable to read token state entries: result ${JSON.stringify(data)} ` ); + } else { + const unmarshalledItems = data.Items.map((item) => unmarshall(item)); + + const tokenStateEntries = z + .array(TokenGenerationStatesClientPurposeEntry) + .safeParse(unmarshalledItems); + + if (!tokenStateEntries.success) { + throw genericInternalError( + `Unable to parse token state entry item: result ${JSON.stringify( + tokenStateEntries + )} - data ${JSON.stringify(data)} ` + ); + } + + if (!data.LastEvaluatedKey) { + return tokenStateEntries.data; + } else { + return [ + ...tokenStateEntries.data, + ...(await runPaginatedQuery( + eserviceId_descriptorId, + dynamoDBClient, + data.LastEvaluatedKey + )), + ]; + } } - return tokenStateEntries.data; - } + }; + + return await runPaginatedQuery( + eserviceId_descriptorId, + dynamoDBClient, + undefined + ); }; export const updateDescriptorStateInTokenGenerationStatesTable = async ( diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index bf32770445..23cbc82c4a 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -400,7 +400,7 @@ describe("integration tests", async () => { expect(result).toEqual([]); }); - it("should return entries if they exist", async () => { + it("should return entries if they exist (no need for pagination)", async () => { const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), kid: `kid ${Math.random()}`, @@ -441,6 +441,46 @@ describe("integration tests", async () => { expect.arrayContaining([tokenStateEntry1, tokenStateEntry2]) ); }); + + it("should return entries if they exist (with pagination)", async () => { + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + + const tokenEntriesLength = 2000; + + const writtenEntries = []; + // eslint-disable-next-line functional/no-let + for (let i = 0; i < tokenEntriesLength; i++) { + const tokenStateEntryPK = makeTokenGenerationStatesClientKidPurposePK( + { + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + } + ); + const tokenStateEntry: TokenGenerationStatesClientPurposeEntry = { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK), + descriptorState: itemState.inactive, + descriptorAudience: "pagopa.it", + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(tokenStateEntry, dynamoDBClient); + // eslint-disable-next-line functional/immutable-data + writtenEntries.push(tokenStateEntry); + } + vi.spyOn(dynamoDBClient, "send"); + const tokenEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + + expect(dynamoDBClient.send).toHaveBeenCalledTimes(2); + expect(tokenEntries).toHaveLength(tokenEntriesLength); + expect(tokenEntries).toEqual(expect.arrayContaining(writtenEntries)); + }); }); describe("updateDescriptorStateInTokenGenerationStatesTable", async () => { From 135b036e2ad066f78cc0b02cab78409c7085ad0a Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Mon, 9 Sep 2024 14:21:26 +0200 Subject: [PATCH 143/241] WIP: add errors --- .../client-assertion-validation/src/errors.ts | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 packages/client-assertion-validation/src/errors.ts diff --git a/packages/client-assertion-validation/src/errors.ts b/packages/client-assertion-validation/src/errors.ts new file mode 100644 index 0000000000..b9cde711eb --- /dev/null +++ b/packages/client-assertion-validation/src/errors.ts @@ -0,0 +1,35 @@ +import { ApiError } from "pagopa-interop-models"; + +export const errorCodes = { + clientAssertionValidationFailure: "0001", + clientAssertionSignatureVerificationFailure: "0002", + platformStateVerificationFailure: "0003", +}; + +export type ErrorCodes = keyof typeof errorCodes; + +// TODO: make api problem? + +export function clientAssertionValidationFailure(): ApiError { + return new ApiError({ + detail: `Client assertion validation failed`, + code: "clientAssertionValidationFailure", + title: "Client assertion validation failed", + }); +} + +export function clientAssertionSignatureVerificationFailure(): ApiError { + return new ApiError({ + detail: `Client assertion signature verification failed`, + code: "clientAssertionSignatureVerificationFailure", + title: "Client assertion signature verification failed", + }); +} + +export function platformStateVerificationFailure(): ApiError { + return new ApiError({ + detail: `Platform state verification failed`, + code: "platformStateVerificationFailure", + title: "Platform state verification failed", + }); +} From ecc1093314456c57e8e3905a89e9586cc552d850 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Mon, 9 Sep 2024 14:21:48 +0200 Subject: [PATCH 144/241] Update package imports --- packages/client-assertion-validation/package.json | 1 + pnpm-lock.yaml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/packages/client-assertion-validation/package.json b/packages/client-assertion-validation/package.json index 38ea2cefcb..9386a1aa3d 100644 --- a/packages/client-assertion-validation/package.json +++ b/packages/client-assertion-validation/package.json @@ -23,6 +23,7 @@ "pagopa-interop-api-clients": "workspace:*", "pagopa-interop-commons-test": "workspace:*", "pagopa-interop-models": "workspace:*", + "pagopa-interop-commons": "workspace:*", "ts-pattern": "5.2.0", "zod": "3.23.8" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ee5d370c2e..91c5e4a2d5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1040,6 +1040,9 @@ importers: pagopa-interop-api-clients: specifier: workspace:* version: link:../api-clients + pagopa-interop-commons: + specifier: workspace:* + version: link:../commons pagopa-interop-commons-test: specifier: workspace:* version: link:../commons-test From 1eb376261a078ca278e81c63e055c231f3df47aa Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 10 Sep 2024 10:36:07 +0200 Subject: [PATCH 145/241] Draft --- .../client-assertion-validation/src/errors.ts | 134 +++++++++++- .../client-assertion-validation/src/utils.ts | 206 +++++++++++------- .../src/validation.ts | 54 +++-- 3 files changed, 296 insertions(+), 98 deletions(-) diff --git a/packages/client-assertion-validation/src/errors.ts b/packages/client-assertion-validation/src/errors.ts index b9cde711eb..c9a3da07b6 100644 --- a/packages/client-assertion-validation/src/errors.ts +++ b/packages/client-assertion-validation/src/errors.ts @@ -4,15 +4,145 @@ export const errorCodes = { clientAssertionValidationFailure: "0001", clientAssertionSignatureVerificationFailure: "0002", platformStateVerificationFailure: "0003", + invalidAssertionType: "0004", + invalidGrantType: "0005", + invalidAudienceFormat: "0006", + invalidAudience: "0007", + invalidClientASsertionFormat: "0008", + unexpectedClientAssertionPayload: "0009", + jtiNotFound: "0010", + issuedAtNotFound: "0011", + expNotFound: "0012", + issuerNotFound: "0013", + subjectNotFound: "0014", + invalidSubject: "0015", + invalidPurposeIdClaimFormat: "0016", + kidNotFound: "0017", }; export type ErrorCodes = keyof typeof errorCodes; // TODO: make api problem? -export function clientAssertionValidationFailure(): ApiError { +export function invalidAssertionType( + assertionType: string +): ApiError { return new ApiError({ - detail: `Client assertion validation failed`, + detail: `Assertion type not valid: ${assertionType}`, + code: "invalidAssertionType", + title: "Client assertion validation failed", + }); +} + +export function invalidGrantType(grantType: string): ApiError { + return new ApiError({ + detail: `Grant type not valid: ${grantType}`, + code: "invalidGrantType", + title: "Client assertion validation failed", + }); +} + +export function invalidAudienceFormat(): ApiError { + return new ApiError({ + detail: `Audience must be an array`, + code: "invalidAudienceFormat", + title: "Client assertion validation failed", + }); +} + +export function invalidAudience(): ApiError { + return new ApiError({ + detail: `Unexpected client assertion audience`, + code: "invalidAudience", + title: "Client assertion validation failed", + }); +} + +export function invalidClientAssertionFormat(): ApiError { + return new ApiError({ + detail: `Invalid format for Client assertion`, + code: "invalidClientASsertionFormat", + title: "Invalid format for Client assertion", + }); +} + +export function unexpectedClientAssertionPayload(): ApiError { + return new ApiError({ + detail: `Invalid format for Client assertion`, + code: "unexpectedClientAssertionPayload", + title: "Invalid format for Client assertion", + }); +} + +export function jtiNotFound(): ApiError { + return new ApiError({ + detail: `Invalid format for Client assertion`, + code: "jtiNotFound", + title: "JTI not found in client assertion", + }); +} + +export function issuedAtNotFound(): ApiError { + return new ApiError({ + detail: `Invalid format for Client assertion`, + code: "issuedAtNotFound", + title: "IAT not found in client assertion", + }); +} + +export function expNotFound(): ApiError { + return new ApiError({ + detail: `Invalid format for Client assertion`, + code: "expNotFound", + title: "EXP not found in client assertion", + }); +} + +export function issuerNotFound(): ApiError { + return new ApiError({ + detail: `Invalid format for Client assertion`, + code: "issuerNotFound", + title: "ISS not found in client assertion", + }); +} + +export function subjectNotFound(): ApiError { + return new ApiError({ + detail: `Invalid format for Client assertion`, + code: "subjectNotFound", + title: "Subject not found in client assertion", + }); +} + +export function invalidSubject(): ApiError { + return new ApiError({ + detail: `Invalid format for Client assertion`, + code: "invalidSubject", + title: "Subject not found in client assertion", + }); +} + +export function invalidPurposeIdClaimFormat(): ApiError { + return new ApiError({ + detail: `Invalid format for Client assertion`, + code: "invalidPurposeIdClaimFormat", + title: "Subject not found in client assertion", + }); +} + +export function kidNotFound(): ApiError { + return new ApiError({ + detail: `Invalid format for Client assertion`, + code: "kidNotFound", + title: "KID not found in client assertion", + }); +} + +export function clientAssertionValidationFailure( + details: string +): ApiError { + return new ApiError({ + detail: `Client assertion validation failed: ${details}`, code: "clientAssertionValidationFailure", title: "Client assertion validation failed", }); diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index e0e10d85a8..95a2a4b2e2 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -1,114 +1,159 @@ +/* eslint-disable functional/immutable-data */ import { authorizationServerApi } from "pagopa-interop-api-clients"; import { decode, JsonWebTokenError, - JwtPayload, NotBeforeError, TokenExpiredError, verify, } from "jsonwebtoken"; -import { PurposeId } from "pagopa-interop-models"; -import { ClientAssertion, ConsumerKey, Key } from "./types.js"; -const CLIENT_ASSERTION_AUDIENCE = ""; // To do: env? +import { ApiError, PurposeId } from "pagopa-interop-models"; +import { ConsumerKey, Key } from "./types.js"; +import { + ErrorCodes, + expNotFound, + issuedAtNotFound, + invalidAssertionType, + invalidAudience, + invalidAudienceFormat, + invalidClientAssertionFormat, + invalidGrantType, + issuerNotFound, + jtiNotFound, + subjectNotFound, + unexpectedClientAssertionPayload, + invalidSubject, + invalidPurposeIdClaimFormat, + kidNotFound, +} from "./errors.js"; +const CLIENT_ASSERTION_AUDIENCE = "DEFAULT_AUDIENCE"; // To do: env? export const validateRequestParameters = ( request: authorizationServerApi.AccessTokenRequest -): boolean => { +): Array> => { const expectedClientAssertionType: string = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; // To do: env? const expectedClientCredentialsGrantType: string = "client_credentials"; // To do: env? + const errors: Array> = []; if (request.client_assertion_type !== expectedClientAssertionType) { - throw Error( - `Unexpected client assertion type. Received ${request.client_assertion_type}` - ); - } else if (request.grant_type !== expectedClientCredentialsGrantType) { - throw Error(`Unexpected grant type. Received ${request.grant_type}`); + // throw Error( + // `Unexpected client assertion type. Received ${request.client_assertion_type}` + // ); + // eslint-disable-next-line functional/immutable-data + errors.push(invalidAssertionType(request.client_assertion_type)); + } + if (request.grant_type !== expectedClientCredentialsGrantType) { + // clientAssertionValidationFailure( + // `Unexpected client assertion type. Received ${request.client_assertion_type}` + // ); + // throw Error(`Unexpected grant type. Received ${request.grant_type}`); + // eslint-disable-next-line functional/immutable-data + errors.push(invalidGrantType(request.grant_type)); } - return true; + return errors; }; export const verifyClientAssertion = ( clientAssertionJws: string, clientId: string | undefined -): ClientAssertion => { +): Array> => { const decoded = decode(clientAssertionJws, { complete: true, json: true }); - const validateAudience = (aud: string | string[] | undefined): string[] => { + console.log("decoded ", decoded); + const validateAudience = ( + aud: string | string[] | undefined + ): { + audienceErrors: Array>; + validatedAudience: string[]; + } => { if (aud === CLIENT_ASSERTION_AUDIENCE) { - return [aud]; + return { audienceErrors: [], validatedAudience: [aud] }; } if (!Array.isArray(aud)) { - throw Error("Audience must be an array"); - } - - if (!aud.includes(CLIENT_ASSERTION_AUDIENCE)) { - throw Error("Unexpected client assertion audience"); + // To do: how to test this if the aud type is string[] + // throw Error("Audience must be an array"); + return { + audienceErrors: [invalidAudienceFormat()], + validatedAudience: [], // to do check fallback value [] + }; + } else { + if (!aud.includes(CLIENT_ASSERTION_AUDIENCE)) { + return { audienceErrors: [invalidAudience()], validatedAudience: [] }; // to do check fallback value [] + // throw Error("Unexpected client assertion audience"); + } + return { audienceErrors: [], validatedAudience: aud }; } - - return aud; }; + const errors: Array> = []; if (!decoded) { - throw Error("Invalid format for Client assertion"); - } + errors.push(invalidClientAssertionFormat()); + } else { + if (typeof decoded.payload === "string") { + errors.push(unexpectedClientAssertionPayload()); // To do: how to test? + } else { + if (!decoded.payload.jti) { + errors.push(jtiNotFound()); + } - if (typeof decoded.payload === "string") { - throw Error("Unexpected client assertion payload"); - } + if (!decoded.payload.iat) { + errors.push(issuedAtNotFound()); + } - if (!decoded.payload.jti) { - throw Error("JTI must be present in client assertion header"); - } + if (!decoded.payload.exp) { + errors.push(expNotFound()); + } - if (!decoded.payload.iat) { - throw Error("IAT must be present in client assertion header"); - } + if (!decoded.payload.iss) { + errors.push(issuerNotFound()); + } - if (!decoded.payload.exp) { - throw Error("EXP must be present in client assertion header"); - } + if (!decoded.payload.sub) { + errors.push(subjectNotFound()); + } - if (!decoded.payload.iss) { - throw Error("ISS must be present in client assertion header"); - } + if (clientId && decoded.payload.sub !== clientId) { + errors.push(invalidSubject()); + } - if (!decoded.payload.sub) { - throw Error("Subject is mandatory in client assertion"); - } + if ( + decoded.payload.purposeId && + !PurposeId.safeParse(decoded.payload.purposeId).success + ) { + errors.push(invalidPurposeIdClaimFormat()); + } - if (clientId && decoded.payload.sub !== clientId) { - throw Error("Client Id must be equal to client assertion subject"); - } + const { audienceErrors } = validateAudience(decoded.payload.aud); - if ( - decoded.payload.purposeId && - !PurposeId.safeParse(decoded.payload.purposeId).success - ) { - throw Error("Wrong purpose id format in client assertion"); - } + errors.push(...audienceErrors); + } - if (!decoded.header.kid) { - throw Error("Kid must be present in client assertion header"); + if (!decoded.header.kid) { + errors.push(kidNotFound()); + } + + // This will return a Client Assertion + // return { + // header: { + // kid: decoded.header.kid, + // alg: decoded.header.alg, + // }, + // payload: { + // sub: decoded.payload.sub, + // purposeId: decoded.payload.purposeId, + // jti: decoded.payload.jti, + // iat: decoded.payload.iat, + // iss: decoded.payload.iss, + // aud: validatedAudience, + // exp: decoded.payload.exp, // TODO Check unit of measure + // }, + // }; } - return { - header: { - kid: decoded.header.kid, - alg: decoded.header.alg, - }, - payload: { - sub: decoded.payload.sub, - purposeId: decoded.payload.purposeId, - jti: decoded.payload.jti, - iat: decoded.payload.iat, - iss: decoded.payload.iss, - aud: validateAudience(decoded.payload.aud), - exp: decoded.payload.exp, // TODO Check unit of measure - }, - }; + return errors; }; export const b64Decode = (str: string): string => @@ -117,7 +162,8 @@ export const b64Decode = (str: string): string => export const verifyClientAssertionSignature = ( clientAssertionJws: string, key: Key -): JwtPayload => { +): Array> => { + // should this return a JwtPayload? try { const result = verify(clientAssertionJws, b64Decode(key.publicKey), { algorithms: [key.algorithm], @@ -125,36 +171,40 @@ export const verifyClientAssertionSignature = ( // TODO Improve this if (typeof result === "string") { - throw Error("Unexpected assertion verification result"); + return [Error("Unexpected assertion verification result")]; } else { - return result; + return []; } } catch (error: unknown) { if (error instanceof TokenExpiredError) { console.log("TokenExpiredError"); - throw error; + return [error]; } else if (error instanceof JsonWebTokenError) { console.log("JsonWebTokenError"); - throw error; + return [error]; } else if (error instanceof NotBeforeError) { console.log("NotBeforeError"); - throw error; + return [error]; } else { console.log("unknown error"); - throw Error("unknown error"); + return [Error("unknown error")]; } } }; -export const assertValidPlatformState = (key: ConsumerKey): void => { +export const assertValidPlatformState = ( + key: ConsumerKey +): Array> => { // To do: is it ok to have these check throwing errors? So that they can be read if needed (instead of just getting false) + const errors: Array> = []; if (key.agreementState !== "ACTIVE") { - throw Error("Invalid agreement state"); + errors.push(Error("Invalid agreement state")); } if (key.descriptorState !== "ACTIVE") { - throw Error("Invalid eservice state"); + errors.push(Error("Invalid eservice state")); } if (key.purposeState !== "ACTIVE") { - throw Error("Invalid purpose state"); + errors.push(Error("Invalid purpose state")); } + return errors; }; diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index 8fba5e0cf4..7fe3b870e8 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -1,51 +1,69 @@ import { authorizationServerApi } from "pagopa-interop-api-clients"; import { match } from "ts-pattern"; -import { clientKind } from "pagopa-interop-models"; +import { ApiError, clientKind } from "pagopa-interop-models"; import { verifyClientAssertionSignature, validateRequestParameters, assertValidPlatformState, verifyClientAssertion, } from "./utils.js"; -import { ApiKey, ClientAssertion, ConsumerKey } from "./types.js"; +import { ApiKey, ConsumerKey } from "./types.js"; +import { ErrorCodes } from "./errors.js"; export const assertValidClientAssertion = async ( request: authorizationServerApi.AccessTokenRequest, key: ConsumerKey | ApiKey // To do use just Key? -): Promise => { - validateRequestParameters(request); +): Promise>> => { + const parametersErrors = validateRequestParameters(request); - const clientAssertionJWT = verifyClientAssertion( + const clientAssertionErrors = verifyClientAssertion( request.client_assertion, request.client_id ); - verifyClientAssertionSignature(request.client_assertion, key); + const clientAssertionSignatureErrors = verifyClientAssertionSignature( + request.client_assertion, + key + ); if (ApiKey.safeParse(key).success) { - return clientAssertionJWT; + return [ + ...parametersErrors, + ...clientAssertionErrors, + ...clientAssertionSignatureErrors, + ]; } - match(key.clientKind) + return match(key.clientKind) .with(clientKind.api, () => { - if (!ApiKey.safeParse(key).success) { - // to do: useful? + const parsingErrors = !ApiKey.safeParse(key).success + ? [Error("parsing")] + : []; - throw Error("parsing"); - } - return true; + return [ + ...parsingErrors, + ...parametersErrors, + ...clientAssertionErrors, + ...clientAssertionSignatureErrors, + ]; }) .with(clientKind.consumer, () => { + const errors: Error[] = []; if (ConsumerKey.safeParse(key).success) { // to do: useful? - assertValidPlatformState(key as ConsumerKey); + // eslint-disable-next-line functional/immutable-data + errors.push(...assertValidPlatformState(key as ConsumerKey)); } else { - throw Error("parsing"); + // eslint-disable-next-line functional/immutable-data + errors.push(Error("parsing")); } - return true; + return [ + ...errors, + ...parametersErrors, + ...clientAssertionErrors, + ...clientAssertionSignatureErrors, + ]; }) .exhaustive(); - - return clientAssertionJWT; }; From a26f95db96042cf02fdeb59ee8e5c313c5d0f26c Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Tue, 10 Sep 2024 11:27:21 +0200 Subject: [PATCH 146/241] WIP: add errors --- .../client-assertion-validation/src/errors.ts | 115 ++++++++++++++++-- .../client-assertion-validation/src/utils.ts | 23 ++-- 2 files changed, 117 insertions(+), 21 deletions(-) diff --git a/packages/client-assertion-validation/src/errors.ts b/packages/client-assertion-validation/src/errors.ts index c9a3da07b6..9ebf2e1c53 100644 --- a/packages/client-assertion-validation/src/errors.ts +++ b/packages/client-assertion-validation/src/errors.ts @@ -18,12 +18,67 @@ export const errorCodes = { invalidSubject: "0015", invalidPurposeIdClaimFormat: "0016", kidNotFound: "0017", + invalidClientAssertionSignatureType: "0018", + tokenExpiredError: "0019", + jsonWebTokenError: "0020", + notBeforeError: "0021", + inactivePurpose: "0022", + inactiveAgreement: "0023", + inactiveEService: "0024", }; export type ErrorCodes = keyof typeof errorCodes; // TODO: make api problem? +// TODO: missing errors: +// - InvalidClientIdFormat +// - ClientAssertionParseFailed +// - ClientAssertionInvalidClaims +// - InvalidSubjectFormat +// - InvalidPurposeIdFormat +// - InvalidHashLength +// - InvalidHashAlgorithm +// - AlgorithmNotFound +// - ExpirationNotFound +// - DigestClaimNotFound +// - InvalidDigestClaims +// - PublicKeyParseFailed +// - ClientAssertionVerificationError +// - InvalidClientAssertionSignature +// - PurposeIdNotProvided ???? +// - PurposeNotFound +// - AlgorithmNotAllowed +// - InvalidAudienceFormat (for audience claim) +// - InvalidDigestFormat +// - InvalidKidFormat + +export function clientAssertionValidationFailure( + details: string +): ApiError { + return new ApiError({ + detail: `Client assertion validation failed: ${details}`, + code: "clientAssertionValidationFailure", + title: "Client assertion validation failed", + }); +} + +export function clientAssertionSignatureVerificationFailure(): ApiError { + return new ApiError({ + detail: `Client assertion signature verification failed`, + code: "clientAssertionSignatureVerificationFailure", + title: "Client assertion signature verification failed", + }); +} + +export function platformStateVerificationFailure(): ApiError { + return new ApiError({ + detail: `Platform state verification failed`, + code: "platformStateVerificationFailure", + title: "Platform state verification failed", + }); +} + export function invalidAssertionType( assertionType: string ): ApiError { @@ -138,28 +193,62 @@ export function kidNotFound(): ApiError { }); } -export function clientAssertionValidationFailure( - details: string +export function invalidClientAssertionSignatureType( + clientAssertionSignatureType: string ): ApiError { return new ApiError({ - detail: `Client assertion validation failed: ${details}`, - code: "clientAssertionValidationFailure", - title: "Client assertion validation failed", + detail: `Client assertion signature's type not valid: ${clientAssertionSignatureType}`, + code: "invalidClientAssertionSignatureType", + title: "Token expired in client assertion signature validation", }); } -export function clientAssertionSignatureVerificationFailure(): ApiError { +export function tokenExpiredError(): ApiError { return new ApiError({ - detail: `Client assertion signature verification failed`, - code: "clientAssertionSignatureVerificationFailure", - title: "Client assertion signature verification failed", + detail: "Token expired in client assertion signature validation", + code: "tokenExpiredError", + title: "Token expired in client assertion signature validation", }); } -export function platformStateVerificationFailure(): ApiError { +export function jsonWebTokenError(): ApiError { return new ApiError({ - detail: `Platform state verification failed`, - code: "platformStateVerificationFailure", - title: "Platform state verification failed", + detail: "Invalid JWT format in client assertion signature validation", + code: "jsonWebTokenError", + title: "Invalid JWT format in client assertion signature validation", + }); +} + +export function notBeforeError(): ApiError { + return new ApiError({ + detail: + "Current time is before not before time in client assertion signature validation", + code: "notBeforeError", + title: + "Current time is before not before time in client assertion signature validation", + }); +} + +export function inactivePurpose(): ApiError { + return new ApiError({ + detail: "Purpose is not active", + code: "inactivePurpose", + title: "Purpose is not active", + }); +} + +export function inactiveEService(): ApiError { + return new ApiError({ + detail: "E-Service is not active", + code: "inactiveEService", + title: "E-Service is not active", + }); +} + +export function inactiveAgreement(): ApiError { + return new ApiError({ + detail: "Agreement is not active", + code: "inactiveAgreement", + title: "Agreement is not active", }); } diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index 95a2a4b2e2..b351e5bdc0 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -25,6 +25,13 @@ import { invalidSubject, invalidPurposeIdClaimFormat, kidNotFound, + inactiveAgreement, + inactiveEService, + tokenExpiredError, + jsonWebTokenError, + notBeforeError, + clientAssertionSignatureVerificationFailure, + invalidClientAssertionSignatureType, } from "./errors.js"; const CLIENT_ASSERTION_AUDIENCE = "DEFAULT_AUDIENCE"; // To do: env? @@ -171,23 +178,23 @@ export const verifyClientAssertionSignature = ( // TODO Improve this if (typeof result === "string") { - return [Error("Unexpected assertion verification result")]; + return [invalidClientAssertionSignatureType(typeof result)]; } else { return []; } } catch (error: unknown) { if (error instanceof TokenExpiredError) { console.log("TokenExpiredError"); - return [error]; + return [tokenExpiredError()]; } else if (error instanceof JsonWebTokenError) { console.log("JsonWebTokenError"); - return [error]; + return [jsonWebTokenError()]; } else if (error instanceof NotBeforeError) { console.log("NotBeforeError"); - return [error]; + return [notBeforeError()]; } else { console.log("unknown error"); - return [Error("unknown error")]; + return [clientAssertionSignatureVerificationFailure()]; } } }; @@ -198,13 +205,13 @@ export const assertValidPlatformState = ( // To do: is it ok to have these check throwing errors? So that they can be read if needed (instead of just getting false) const errors: Array> = []; if (key.agreementState !== "ACTIVE") { - errors.push(Error("Invalid agreement state")); + errors.push(inactiveAgreement()); } if (key.descriptorState !== "ACTIVE") { - errors.push(Error("Invalid eservice state")); + errors.push(inactiveEService()); } if (key.purposeState !== "ACTIVE") { - errors.push(Error("Invalid purpose state")); + errors.push(inactiveEService()); } return errors; }; From e22b0413b9485123388e312a52e9f916a58172b8 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 10 Sep 2024 11:43:19 +0200 Subject: [PATCH 147/241] Update errors --- .../client-assertion-validation/src/errors.ts | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/packages/client-assertion-validation/src/errors.ts b/packages/client-assertion-validation/src/errors.ts index 9ebf2e1c53..ed4f597902 100644 --- a/packages/client-assertion-validation/src/errors.ts +++ b/packages/client-assertion-validation/src/errors.ts @@ -131,65 +131,67 @@ export function unexpectedClientAssertionPayload(): ApiError { export function jtiNotFound(): ApiError { return new ApiError({ - detail: `Invalid format for Client assertion`, + detail: `jti not found in client assertion`, code: "jtiNotFound", - title: "JTI not found in client assertion", + title: "jti not found", }); } export function issuedAtNotFound(): ApiError { return new ApiError({ - detail: `Invalid format for Client assertion`, + detail: `iat not found in client assertion`, code: "issuedAtNotFound", - title: "IAT not found in client assertion", + title: "iat not found", }); } export function expNotFound(): ApiError { return new ApiError({ - detail: `Invalid format for Client assertion`, + detail: `exp not found in client assertion`, code: "expNotFound", - title: "EXP not found in client assertion", + title: "exp not found", }); } export function issuerNotFound(): ApiError { return new ApiError({ - detail: `Invalid format for Client assertion`, + detail: `Issuer not found in client assertion`, code: "issuerNotFound", - title: "ISS not found in client assertion", + title: "iss not found", }); } export function subjectNotFound(): ApiError { return new ApiError({ - detail: `Invalid format for Client assertion`, + detail: `Subject not found in client assertion`, code: "subjectNotFound", - title: "Subject not found in client assertion", + title: "Subject not found", }); } -export function invalidSubject(): ApiError { +export function invalidSubject(subject?: string): ApiError { return new ApiError({ - detail: `Invalid format for Client assertion`, + detail: `Subject claim value ${subject} does not correspond to provided client_id parameter`, code: "invalidSubject", - title: "Subject not found in client assertion", + title: "Invalid subject", }); } -export function invalidPurposeIdClaimFormat(): ApiError { +export function invalidPurposeIdClaimFormat( + purposeId: string +): ApiError { return new ApiError({ - detail: `Invalid format for Client assertion`, + detail: `Purpose Id claim ${purposeId} is not a valid UUID`, code: "invalidPurposeIdClaimFormat", - title: "Subject not found in client assertion", + title: "Invalid purposeId claim format", }); } export function kidNotFound(): ApiError { return new ApiError({ - detail: `Invalid format for Client assertion`, + detail: `kid not found in client assertion`, code: "kidNotFound", - title: "KID not found in client assertion", + title: "kid not found", }); } From b34ed350d61e6eeded3a3863a48ec81513182db4 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 10 Sep 2024 11:43:34 +0200 Subject: [PATCH 148/241] Update utils --- .../client-assertion-validation/src/utils.ts | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index b351e5bdc0..95518cb54a 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -33,7 +33,7 @@ import { clientAssertionSignatureVerificationFailure, invalidClientAssertionSignatureType, } from "./errors.js"; -const CLIENT_ASSERTION_AUDIENCE = "DEFAULT_AUDIENCE"; // To do: env? +const CLIENT_ASSERTION_AUDIENCE = "test.interop.pagopa.it"; // To do: env? export const validateRequestParameters = ( request: authorizationServerApi.AccessTokenRequest @@ -44,17 +44,10 @@ export const validateRequestParameters = ( const errors: Array> = []; if (request.client_assertion_type !== expectedClientAssertionType) { - // throw Error( - // `Unexpected client assertion type. Received ${request.client_assertion_type}` - // ); // eslint-disable-next-line functional/immutable-data errors.push(invalidAssertionType(request.client_assertion_type)); } if (request.grant_type !== expectedClientCredentialsGrantType) { - // clientAssertionValidationFailure( - // `Unexpected client assertion type. Received ${request.client_assertion_type}` - // ); - // throw Error(`Unexpected grant type. Received ${request.grant_type}`); // eslint-disable-next-line functional/immutable-data errors.push(invalidGrantType(request.grant_type)); } @@ -65,10 +58,10 @@ export const validateRequestParameters = ( export const verifyClientAssertion = ( clientAssertionJws: string, clientId: string | undefined + // eslint-disable-next-line sonarjs/cognitive-complexity ): Array> => { const decoded = decode(clientAssertionJws, { complete: true, json: true }); - console.log("decoded ", decoded); const validateAudience = ( aud: string | string[] | undefined ): { @@ -80,8 +73,6 @@ export const verifyClientAssertion = ( } if (!Array.isArray(aud)) { - // To do: how to test this if the aud type is string[] - // throw Error("Audience must be an array"); return { audienceErrors: [invalidAudienceFormat()], validatedAudience: [], // to do check fallback value [] @@ -89,7 +80,6 @@ export const verifyClientAssertion = ( } else { if (!aud.includes(CLIENT_ASSERTION_AUDIENCE)) { return { audienceErrors: [invalidAudience()], validatedAudience: [] }; // to do check fallback value [] - // throw Error("Unexpected client assertion audience"); } return { audienceErrors: [], validatedAudience: aud }; } @@ -123,14 +113,14 @@ export const verifyClientAssertion = ( } if (clientId && decoded.payload.sub !== clientId) { - errors.push(invalidSubject()); + errors.push(invalidSubject(decoded.payload.sub)); } if ( decoded.payload.purposeId && !PurposeId.safeParse(decoded.payload.purposeId).success ) { - errors.push(invalidPurposeIdClaimFormat()); + errors.push(invalidPurposeIdClaimFormat(decoded.payload.purposeId)); } const { audienceErrors } = validateAudience(decoded.payload.aud); From 3c5ad7c8ed27a80ad63b6e4ab9261ce842567c3e Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 10 Sep 2024 11:44:03 +0200 Subject: [PATCH 149/241] Draft test --- .../test/sample.test.ts | 219 ++++++++++++++---- .../client-assertion-validation/test/utils.ts | 41 ++++ 2 files changed, 215 insertions(+), 45 deletions(-) create mode 100644 packages/client-assertion-validation/test/utils.ts diff --git a/packages/client-assertion-validation/test/sample.test.ts b/packages/client-assertion-validation/test/sample.test.ts index 7dcb245cd9..34244a2d08 100644 --- a/packages/client-assertion-validation/test/sample.test.ts +++ b/packages/client-assertion-validation/test/sample.test.ts @@ -1,49 +1,178 @@ -import { describe, it } from "vitest"; -import { clientKind, generateId } from "pagopa-interop-models"; -import { ConsumerKey, Key, ApiKey } from ".././src/types.js"; +import crypto from "crypto"; +import { describe, expect, it } from "vitest"; +import { ClientId, generateId } from "pagopa-interop-models"; +import * as jwt from "jsonwebtoken"; +import { verifyClientAssertion } from "../src/utils.js"; +import { + expNotFound, + invalidAudience, + invalidAudienceFormat, + invalidClientAssertionFormat, + invalidPurposeIdClaimFormat, + invalidSubject, + issuedAtNotFound, + issuerNotFound, + jtiNotFound, + subjectNotFound, + unexpectedClientAssertionPayload, +} from "../src/errors.js"; +import { getMockClientAssertion } from "./utils.js"; describe("test", () => { - it("zod inheritance", () => { - const key: Key = { - clientId: generateId(), - consumerId: generateId(), - kidWithPurposeId: "123", - publicKey: "123", - algorithm: "RS256", - }; - - const cKey: ConsumerKey = { - ...key, - clientKind: "Consumer", - purposeId: generateId(), - purposeState: "ACTIVE", - agreementId: generateId(), - agreementState: "ACTIVE", - eServiceId: generateId(), - eServiceState: "ACTIVE", - }; - - const aKey: ApiKey = { - ...key, - clientKind: clientKind.api, - }; - - doStuff(cKey); - doStuff(aKey); - doStuff(key); + describe("verifyClientAssertion", () => { + it("invalidAudienceFormat", () => { + const keySet = crypto.generateKeyPairSync("rsa", { + modulusLength: 2048, + }); + + const payload = { + iss: generateId(), + sub: generateId(), + aud: "not an array", + exp: 60, + jti: generateId(), + iat: 5, + }; + + const options: jwt.SignOptions = { + header: { + kid: generateId(), + alg: "RS256", + }, + }; + const jws = jwt.sign(payload, keySet.privateKey, options); + const errors = verifyClientAssertion(jws, undefined); + expect(errors).toHaveLength(1); + expect(errors[0]).toEqual(invalidAudienceFormat()); + }); + + it("invalidAudience", () => { + const a = getMockClientAssertion({ + payload: { aud: ["random"] }, + customClaims: { key: 1 }, + }); + const errors = verifyClientAssertion(a, undefined); + expect(errors).toHaveLength(1); + expect(errors[0]).toEqual(invalidAudience()); + }); + + it("invalidClientAssertionFormat", () => { + const errors = verifyClientAssertion("not a jwt", undefined); + expect(errors).toHaveLength(1); + expect(errors[0]).toEqual(invalidClientAssertionFormat()); + }); + + it.skip("unexpectedClientAssertionPayload", () => { + // to do: how to test? In this case the payload should be a string + + const key = crypto.generateKeyPairSync("rsa", { + modulusLength: 2048, + }).privateKey; + + const options: jwt.SignOptions = { + header: { + kid: generateId(), + alg: "RS256", + }, + }; + const jws = jwt.sign("actualPayload", key, options); + + const errors = verifyClientAssertion(jws, undefined); + expect(errors).toHaveLength(1); + expect(errors[0]).toEqual(unexpectedClientAssertionPayload()); + }); + + it("jtiNotFound", () => { + const a = getMockClientAssertion({ + payload: { jti: undefined }, + customClaims: { key: 1 }, + }); + const errors = verifyClientAssertion(a, undefined); + + expect(errors).toHaveLength(1); + expect(errors[0]).toEqual(jtiNotFound()); + }); + + it.skip("iatNotFound", () => { + // to do: how to test? The sign function automatically adds iat if not present + + const a = getMockClientAssertion({ + payload: {}, + customClaims: { key: 1 }, + }); + const errors = verifyClientAssertion(a, undefined); + // console.log(errors); + expect(errors).toHaveLength(1); + expect(errors[0]).toEqual(issuedAtNotFound()); + // console.log("error code: ", errors[0].code); + }); + + it("expNotFound", () => { + const keySet = crypto.generateKeyPairSync("rsa", { + modulusLength: 2048, + }); + + const payload = { + iss: generateId(), + sub: generateId(), + aud: ["test.interop.pagopa.it"], + jti: generateId(), + iat: 5, + }; + + const options: jwt.SignOptions = { + header: { + kid: generateId(), + alg: "RS256", + }, + }; + const jws = jwt.sign(payload, keySet.privateKey, options); + const errors = verifyClientAssertion(jws, undefined); + expect(errors).toHaveLength(1); + expect(errors[0]).toEqual(expNotFound()); + }); + + it("issuerNotFound", () => { + const jws = getMockClientAssertion({ + payload: { iss: undefined }, + customClaims: {}, + }); + const errors = verifyClientAssertion(jws, undefined); + expect(errors).toHaveLength(1); + expect(errors[0]).toEqual(issuerNotFound()); + }); + + it("subjectNotFound", () => { + const jws = getMockClientAssertion({ + payload: { sub: undefined }, + customClaims: {}, + }); + const errors = verifyClientAssertion(jws, undefined); + expect(errors).toHaveLength(1); + expect(errors[0]).toEqual(subjectNotFound()); + }); + + it("invalidSubject", () => { + const subject = generateId(); + const jws = getMockClientAssertion({ + payload: { sub: subject }, + customClaims: {}, + }); + const errors = verifyClientAssertion(jws, generateId()); + expect(errors).toHaveLength(1); + expect(errors[0]).toEqual(invalidSubject(subject)); + }); + + it("invalidPurposeIdClaimFormat", () => { + const notPurposeId = "not a purpose id"; + const jws = getMockClientAssertion({ + payload: {}, + customClaims: { purposeId: notPurposeId }, + }); + const errors = verifyClientAssertion(jws, undefined); + + expect(errors).toHaveLength(1); + expect(errors[0]).toEqual(invalidPurposeIdClaimFormat(notPurposeId)); + }); }); }); - -const doStuff = (input: Key): void => { - // console.log(typeof input); - // const res = ConsumerKey.safeParse(input); - // console.log(res.error); - if (ConsumerKey.safeParse(input).success) { - console.log("consumer"); - } else if (ApiKey.safeParse(input).success) { - console.log("api"); - } else { - console.log("no idea"); - } - console.log(input); -}; diff --git a/packages/client-assertion-validation/test/utils.ts b/packages/client-assertion-validation/test/utils.ts new file mode 100644 index 0000000000..532bf538e9 --- /dev/null +++ b/packages/client-assertion-validation/test/utils.ts @@ -0,0 +1,41 @@ +import crypto from "crypto"; +import * as jwt from "jsonwebtoken"; +import { ClientId, generateId } from "pagopa-interop-models"; +import { ClientAssertionPayload } from ".././src/types"; + +export const getMockClientAssertion = ({ + payload, + customClaims, +}: { + payload: Partial; + customClaims: { [k: string]: unknown }; +}): string => { + const clientId = generateId(); + const defaultPayload = { + iss: clientId, + sub: clientId, + aud: ["test.interop.pagopa.it"], + exp: 60, + jti: generateId(), + iat: 5, + // ...customClaims, // TO DO: how many custom claims? Examples? + }; + + const actualPayload = { + ...defaultPayload, + ...payload, + ...customClaims, + }; + + const keySet = crypto.generateKeyPairSync("rsa", { + modulusLength: 2048, + }); + + const options: jwt.SignOptions = { + header: { + kid: generateId(), + alg: "RS256", + }, + }; + return jwt.sign(actualPayload, keySet.privateKey, options); +}; From 02eeeb4744a83d35f9447b66214b62a8c49f2174 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 10 Sep 2024 11:54:54 +0200 Subject: [PATCH 150/241] Add placeholders for tests --- .../test/sample.test.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/packages/client-assertion-validation/test/sample.test.ts b/packages/client-assertion-validation/test/sample.test.ts index 34244a2d08..95edd1bf2e 100644 --- a/packages/client-assertion-validation/test/sample.test.ts +++ b/packages/client-assertion-validation/test/sample.test.ts @@ -19,6 +19,15 @@ import { import { getMockClientAssertion } from "./utils.js"; describe("test", () => { + describe("validateRequestParameters", () => { + it("invalidAssertionType", () => { + expect(1).toBe(1); + }); + it("invalidGrantType", () => { + expect(1).toBe(1); + }); + }); + describe("verifyClientAssertion", () => { it("invalidAudienceFormat", () => { const keySet = crypto.generateKeyPairSync("rsa", { @@ -175,4 +184,34 @@ describe("test", () => { expect(errors[0]).toEqual(invalidPurposeIdClaimFormat(notPurposeId)); }); }); + + describe("verifyClientAssertionSignature", () => { + it("invalidClientAssertionSignatureType", () => { + expect(1).toBe(1); + }); + it("tokenExpiredError", () => { + expect(1).toBe(1); + }); + it("jsonWebTokenError", () => { + expect(1).toBe(1); + }); + it("notBeforeError", () => { + expect(1).toBe(1); + }); + it("clientAssertionSignatureVerificationFailure", () => { + expect(1).toBe(1); + }); + }); + + describe("assertValidPlatformStates", () => { + it("inactiveAgreement", () => { + expect(1).toBe(1); + }); + it("inactiveEservice", () => { + expect(1).toBe(1); + }); + it("inactivePurpose", () => { + expect(1).toBe(1); + }); + }); }); From 8562a57585b4ac91872f007633a900407abee319 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 10 Sep 2024 16:39:23 +0200 Subject: [PATCH 151/241] Add type --- packages/client-assertion-validation/src/types.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/client-assertion-validation/src/types.ts b/packages/client-assertion-validation/src/types.ts index c0ce589752..6c8055e720 100644 --- a/packages/client-assertion-validation/src/types.ts +++ b/packages/client-assertion-validation/src/types.ts @@ -1,6 +1,7 @@ import { authorizationManagementApi } from "pagopa-interop-api-clients"; import { AgreementId, + ApiError, ClientId, clientKind, EServiceId, @@ -8,6 +9,7 @@ import { TenantId, } from "pagopa-interop-models"; import { z } from "zod"; +import { ErrorCodes } from "./errors.js"; export const ClientAssertionHeader = z.object({ kid: z.string(), @@ -56,3 +58,13 @@ export const ApiKey = Key.extend({ clientKind: z.literal(clientKind.api), }); export type ApiKey = z.infer; + +export type ValidationResult = + | { errors: undefined; data: ClientAssertion } + | { errors: Array>; data: undefined }; + +// alternatively +export type ValidationResult2 = { + errors: Array>; + data?: ClientAssertion; +}; From 060e2b73bb13d1f5fd8ea75bae8852888b279204 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 10 Sep 2024 16:39:40 +0200 Subject: [PATCH 152/241] Refactor --- .../client-assertion-validation/src/utils.ts | 122 ++++++++++-------- .../src/validation.ts | 16 +-- 2 files changed, 74 insertions(+), 64 deletions(-) diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index 95518cb54a..010dd0f313 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable functional/immutable-data */ import { authorizationServerApi } from "pagopa-interop-api-clients"; import { @@ -8,7 +9,12 @@ import { verify, } from "jsonwebtoken"; import { ApiError, PurposeId } from "pagopa-interop-models"; -import { ConsumerKey, Key } from "./types.js"; +import { + ClientAssertion, + ConsumerKey, + Key, + ValidationResult, +} from "./types.js"; import { ErrorCodes, expNotFound, @@ -27,6 +33,7 @@ import { kidNotFound, inactiveAgreement, inactiveEService, + inactivePurpose, tokenExpiredError, jsonWebTokenError, notBeforeError, @@ -34,20 +41,19 @@ import { invalidClientAssertionSignatureType, } from "./errors.js"; const CLIENT_ASSERTION_AUDIENCE = "test.interop.pagopa.it"; // To do: env? +const EXPECTED_CLIENT_ASSERTION_TYPE = + "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; // To do: env? +const EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE = "client_credentials"; // To do: env? export const validateRequestParameters = ( request: authorizationServerApi.AccessTokenRequest ): Array> => { - const expectedClientAssertionType: string = - "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; // To do: env? - const expectedClientCredentialsGrantType: string = "client_credentials"; // To do: env? - const errors: Array> = []; - if (request.client_assertion_type !== expectedClientAssertionType) { + if (request.client_assertion_type !== EXPECTED_CLIENT_ASSERTION_TYPE) { // eslint-disable-next-line functional/immutable-data errors.push(invalidAssertionType(request.client_assertion_type)); } - if (request.grant_type !== expectedClientCredentialsGrantType) { + if (request.grant_type !== EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE) { // eslint-disable-next-line functional/immutable-data errors.push(invalidGrantType(request.grant_type)); } @@ -55,42 +61,44 @@ export const validateRequestParameters = ( return errors; }; +const validateAudience = ( + aud: string | string[] | undefined +): { + audienceErrors: Array>; + validatedAudience: string[]; +} => { + if (aud === CLIENT_ASSERTION_AUDIENCE) { + return { audienceErrors: [], validatedAudience: [aud] }; + } + + if (!Array.isArray(aud)) { + return { + audienceErrors: [invalidAudienceFormat()], + validatedAudience: [], // to do check fallback value [] + }; + } else { + if (!aud.includes(CLIENT_ASSERTION_AUDIENCE)) { + return { audienceErrors: [invalidAudience()], validatedAudience: [] }; // to do check fallback value [] + } + return { audienceErrors: [], validatedAudience: aud }; + } +}; + export const verifyClientAssertion = ( clientAssertionJws: string, clientId: string | undefined // eslint-disable-next-line sonarjs/cognitive-complexity -): Array> => { +): ValidationResult => { const decoded = decode(clientAssertionJws, { complete: true, json: true }); - const validateAudience = ( - aud: string | string[] | undefined - ): { - audienceErrors: Array>; - validatedAudience: string[]; - } => { - if (aud === CLIENT_ASSERTION_AUDIENCE) { - return { audienceErrors: [], validatedAudience: [aud] }; - } - - if (!Array.isArray(aud)) { - return { - audienceErrors: [invalidAudienceFormat()], - validatedAudience: [], // to do check fallback value [] - }; - } else { - if (!aud.includes(CLIENT_ASSERTION_AUDIENCE)) { - return { audienceErrors: [invalidAudience()], validatedAudience: [] }; // to do check fallback value [] - } - return { audienceErrors: [], validatedAudience: aud }; - } - }; - const errors: Array> = []; if (!decoded) { errors.push(invalidClientAssertionFormat()); + return { errors, data: undefined }; } else { if (typeof decoded.payload === "string") { errors.push(unexpectedClientAssertionPayload()); // To do: how to test? + return { errors, data: undefined }; } else { if (!decoded.payload.jti) { errors.push(jtiNotFound()); @@ -123,34 +131,36 @@ export const verifyClientAssertion = ( errors.push(invalidPurposeIdClaimFormat(decoded.payload.purposeId)); } - const { audienceErrors } = validateAudience(decoded.payload.aud); + if (!decoded.header.kid) { + errors.push(kidNotFound()); + } + const { audienceErrors, validatedAudience } = validateAudience( + decoded.payload.aud + ); errors.push(...audienceErrors); - } - if (!decoded.header.kid) { - errors.push(kidNotFound()); - } + const result: ClientAssertion = { + header: { + kid: decoded.header.kid!, + alg: decoded.header.alg, + }, + payload: { + sub: decoded.payload.sub!, + purposeId: decoded.payload.purposeId, + jti: decoded.payload.jti!, + iat: decoded.payload.iat!, + iss: decoded.payload.iss!, + aud: validatedAudience, + exp: decoded.payload.exp!, // TODO Check unit of measure + }, + }; - // This will return a Client Assertion - // return { - // header: { - // kid: decoded.header.kid, - // alg: decoded.header.alg, - // }, - // payload: { - // sub: decoded.payload.sub, - // purposeId: decoded.payload.purposeId, - // jti: decoded.payload.jti, - // iat: decoded.payload.iat, - // iss: decoded.payload.iss, - // aud: validatedAudience, - // exp: decoded.payload.exp, // TODO Check unit of measure - // }, - // }; + return errors.length === 0 + ? { errors: undefined, data: result } + : { errors, data: undefined }; + } } - - return errors; }; export const b64Decode = (str: string): string => @@ -160,7 +170,7 @@ export const verifyClientAssertionSignature = ( clientAssertionJws: string, key: Key ): Array> => { - // should this return a JwtPayload? + // todo: should this return a JwtPayload? Probably not try { const result = verify(clientAssertionJws, b64Decode(key.publicKey), { algorithms: [key.algorithm], @@ -201,7 +211,7 @@ export const assertValidPlatformState = ( errors.push(inactiveEService()); } if (key.purposeState !== "ACTIVE") { - errors.push(inactiveEService()); + errors.push(inactivePurpose()); } return errors; }; diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index 7fe3b870e8..8e47c610a4 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -12,24 +12,24 @@ import { ErrorCodes } from "./errors.js"; export const assertValidClientAssertion = async ( request: authorizationServerApi.AccessTokenRequest, - key: ConsumerKey | ApiKey // To do use just Key? + key: ConsumerKey | ApiKey // Todo use just Key? ): Promise>> => { const parametersErrors = validateRequestParameters(request); - const clientAssertionErrors = verifyClientAssertion( - request.client_assertion, - request.client_id - ); + const { errors: clientAssertionVerificationErrors, data: jwt } = + verifyClientAssertion(request.client_assertion, request.client_id); const clientAssertionSignatureErrors = verifyClientAssertionSignature( request.client_assertion, key ); + // todo exit here if there are any errors so far? + if (ApiKey.safeParse(key).success) { return [ ...parametersErrors, - ...clientAssertionErrors, + ...(clientAssertionVerificationErrors || []), ...clientAssertionSignatureErrors, ]; } @@ -43,7 +43,7 @@ export const assertValidClientAssertion = async ( return [ ...parsingErrors, ...parametersErrors, - ...clientAssertionErrors, + ...(clientAssertionVerificationErrors || []), ...clientAssertionSignatureErrors, ]; }) @@ -61,7 +61,7 @@ export const assertValidClientAssertion = async ( return [ ...errors, ...parametersErrors, - ...clientAssertionErrors, + ...(clientAssertionVerificationErrors || []), ...clientAssertionSignatureErrors, ]; }) From 43d252a976b4c38afe86aa0b4c86aadc151a6a00 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 10 Sep 2024 16:39:49 +0200 Subject: [PATCH 153/241] Update tests --- .../test/sample.test.ts | 75 +++++++++++++------ 1 file changed, 51 insertions(+), 24 deletions(-) diff --git a/packages/client-assertion-validation/test/sample.test.ts b/packages/client-assertion-validation/test/sample.test.ts index 95edd1bf2e..d6aa64798a 100644 --- a/packages/client-assertion-validation/test/sample.test.ts +++ b/packages/client-assertion-validation/test/sample.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ import crypto from "crypto"; import { describe, expect, it } from "vitest"; import { ClientId, generateId } from "pagopa-interop-models"; @@ -50,9 +51,10 @@ describe("test", () => { }, }; const jws = jwt.sign(payload, keySet.privateKey, options); - const errors = verifyClientAssertion(jws, undefined); + const { errors } = verifyClientAssertion(jws, undefined); + expect(errors).toBeDefined(); expect(errors).toHaveLength(1); - expect(errors[0]).toEqual(invalidAudienceFormat()); + expect(errors![0]).toEqual(invalidAudienceFormat()); }); it("invalidAudience", () => { @@ -60,15 +62,34 @@ describe("test", () => { payload: { aud: ["random"] }, customClaims: { key: 1 }, }); - const errors = verifyClientAssertion(a, undefined); + const { errors } = verifyClientAssertion(a, undefined); + expect(errors).toBeDefined(); expect(errors).toHaveLength(1); - expect(errors[0]).toEqual(invalidAudience()); + expect(errors![0]).toEqual(invalidAudience()); }); it("invalidClientAssertionFormat", () => { - const errors = verifyClientAssertion("not a jwt", undefined); + const { errors } = verifyClientAssertion("not a jwt", undefined); + expect(errors).toBeDefined(); expect(errors).toHaveLength(1); - expect(errors[0]).toEqual(invalidClientAssertionFormat()); + expect(errors![0]).toEqual(invalidClientAssertionFormat()); + }); + + it("invalidClientAssertionFormat", () => { + const { errors } = verifyClientAssertion("not.a.jwt", undefined); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors![0]).toEqual(invalidClientAssertionFormat()); + }); + + it("invalidClientAssertionFormat", () => { + const { errors } = verifyClientAssertion( + `${generateId()}.${generateId()}`, + undefined + ); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors![0]).toEqual(invalidClientAssertionFormat()); }); it.skip("unexpectedClientAssertionPayload", () => { @@ -86,9 +107,10 @@ describe("test", () => { }; const jws = jwt.sign("actualPayload", key, options); - const errors = verifyClientAssertion(jws, undefined); + const { errors } = verifyClientAssertion(jws, undefined); + expect(errors).toBeDefined(); expect(errors).toHaveLength(1); - expect(errors[0]).toEqual(unexpectedClientAssertionPayload()); + expect(errors![0]).toEqual(unexpectedClientAssertionPayload()); }); it("jtiNotFound", () => { @@ -96,10 +118,10 @@ describe("test", () => { payload: { jti: undefined }, customClaims: { key: 1 }, }); - const errors = verifyClientAssertion(a, undefined); - + const { errors } = verifyClientAssertion(a, undefined); + expect(errors).toBeDefined(); expect(errors).toHaveLength(1); - expect(errors[0]).toEqual(jtiNotFound()); + expect(errors![0]).toEqual(jtiNotFound()); }); it.skip("iatNotFound", () => { @@ -109,10 +131,11 @@ describe("test", () => { payload: {}, customClaims: { key: 1 }, }); - const errors = verifyClientAssertion(a, undefined); + const { errors } = verifyClientAssertion(a, undefined); // console.log(errors); + expect(errors).toBeDefined(); expect(errors).toHaveLength(1); - expect(errors[0]).toEqual(issuedAtNotFound()); + expect(errors![0]).toEqual(issuedAtNotFound()); // console.log("error code: ", errors[0].code); }); @@ -136,9 +159,10 @@ describe("test", () => { }, }; const jws = jwt.sign(payload, keySet.privateKey, options); - const errors = verifyClientAssertion(jws, undefined); + const { errors } = verifyClientAssertion(jws, undefined); + expect(errors).toBeDefined(); expect(errors).toHaveLength(1); - expect(errors[0]).toEqual(expNotFound()); + expect(errors![0]).toEqual(expNotFound()); }); it("issuerNotFound", () => { @@ -146,9 +170,10 @@ describe("test", () => { payload: { iss: undefined }, customClaims: {}, }); - const errors = verifyClientAssertion(jws, undefined); + const { errors } = verifyClientAssertion(jws, undefined); + expect(errors).toBeDefined(); expect(errors).toHaveLength(1); - expect(errors[0]).toEqual(issuerNotFound()); + expect(errors![0]).toEqual(issuerNotFound()); }); it("subjectNotFound", () => { @@ -156,9 +181,10 @@ describe("test", () => { payload: { sub: undefined }, customClaims: {}, }); - const errors = verifyClientAssertion(jws, undefined); + const { errors } = verifyClientAssertion(jws, undefined); + expect(errors).toBeDefined(); expect(errors).toHaveLength(1); - expect(errors[0]).toEqual(subjectNotFound()); + expect(errors![0]).toEqual(subjectNotFound()); }); it("invalidSubject", () => { @@ -167,9 +193,10 @@ describe("test", () => { payload: { sub: subject }, customClaims: {}, }); - const errors = verifyClientAssertion(jws, generateId()); + const { errors } = verifyClientAssertion(jws, generateId()); + expect(errors).toBeDefined(); expect(errors).toHaveLength(1); - expect(errors[0]).toEqual(invalidSubject(subject)); + expect(errors![0]).toEqual(invalidSubject(subject)); }); it("invalidPurposeIdClaimFormat", () => { @@ -178,10 +205,10 @@ describe("test", () => { payload: {}, customClaims: { purposeId: notPurposeId }, }); - const errors = verifyClientAssertion(jws, undefined); - + const { errors } = verifyClientAssertion(jws, undefined); + expect(errors).toBeDefined(); expect(errors).toHaveLength(1); - expect(errors[0]).toEqual(invalidPurposeIdClaimFormat(notPurposeId)); + expect(errors![0]).toEqual(invalidPurposeIdClaimFormat(notPurposeId)); }); }); From d09d7a2287762950bb482b59dbb77c4ab49b3588 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 10 Sep 2024 16:39:56 +0200 Subject: [PATCH 154/241] Update comment --- .../client-assertion-validation/src/errors.ts | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/packages/client-assertion-validation/src/errors.ts b/packages/client-assertion-validation/src/errors.ts index ed4f597902..acf022c6f8 100644 --- a/packages/client-assertion-validation/src/errors.ts +++ b/packages/client-assertion-validation/src/errors.ts @@ -32,26 +32,24 @@ export type ErrorCodes = keyof typeof errorCodes; // TODO: make api problem? // TODO: missing errors: -// - InvalidClientIdFormat -// - ClientAssertionParseFailed -// - ClientAssertionInvalidClaims -// - InvalidSubjectFormat -// - InvalidPurposeIdFormat -// - InvalidHashLength -// - InvalidHashAlgorithm -// - AlgorithmNotFound -// - ExpirationNotFound -// - DigestClaimNotFound -// - InvalidDigestClaims -// - PublicKeyParseFailed -// - ClientAssertionVerificationError -// - InvalidClientAssertionSignature -// - PurposeIdNotProvided ???? -// - PurposeNotFound -// - AlgorithmNotAllowed -// - InvalidAudienceFormat (for audience claim) -// - InvalidDigestFormat -// - InvalidKidFormat +// - InvalidClientIdFormat -> check on uuid +// - ClientAssertionParseFailed -> already handled in invalidClientAssertionFormat +// - ClientAssertionInvalidClaims -> should be covered by individual checks +// - InvalidSubjectFormat -> check on uuid of subject claim +// - InvalidPurposeIdFormat -> check on uuid of purposeId claim +// - DigestClaimNotFound -> check if custom claim digest exists +// - InvalidDigestClaims -> check if digest has {alg, value} +// - InvalidDigestFormat -> probably overlapping with previous +// - InvalidHashLength -> check on the length of digest.value (digest is a custom claim) +// - InvalidHashAlgorithm -> check if digest.alg is sha256 +// - AlgorithmNotFound -> check if header.alg is present +// - AlgorithmNotAllowed -> check if (header.alg === RSA) +// - PublicKeyParseFailed -> out of scope for this module +// - ClientAssertionVerificationError -> maybe too generic +// - InvalidClientAssertionSignature -> maybe already covered by existing cases +// - PurposeIdNotProvided -> based on entry type (Api client doesn't need purposeId) +// - PurposeNotFound -> related to previous, check if there is a purpose entry for that purposeId (in platform states) +// - InvalidKidFormat -> Verify that kid does not contain special characters export function clientAssertionValidationFailure( details: string From 3bf72b3fef0fd83bf1cc770181bd8f995727d562 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Wed, 11 Sep 2024 09:46:50 +0200 Subject: [PATCH 155/241] Fix errors data --- .../client-assertion-validation/src/errors.ts | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/packages/client-assertion-validation/src/errors.ts b/packages/client-assertion-validation/src/errors.ts index acf022c6f8..173a15cc05 100644 --- a/packages/client-assertion-validation/src/errors.ts +++ b/packages/client-assertion-validation/src/errors.ts @@ -83,7 +83,7 @@ export function invalidAssertionType( return new ApiError({ detail: `Assertion type not valid: ${assertionType}`, code: "invalidAssertionType", - title: "Client assertion validation failed", + title: "Assertion type not valid", }); } @@ -91,7 +91,7 @@ export function invalidGrantType(grantType: string): ApiError { return new ApiError({ detail: `Grant type not valid: ${grantType}`, code: "invalidGrantType", - title: "Client assertion validation failed", + title: "Grant type not valid", }); } @@ -99,15 +99,15 @@ export function invalidAudienceFormat(): ApiError { return new ApiError({ detail: `Audience must be an array`, code: "invalidAudienceFormat", - title: "Client assertion validation failed", + title: "Invalid audience format", }); } export function invalidAudience(): ApiError { return new ApiError({ - detail: `Unexpected client assertion audience`, + detail: "Unexpected client assertion audience", code: "invalidAudience", - title: "Client assertion validation failed", + title: "Invalid audience", }); } @@ -121,33 +121,33 @@ export function invalidClientAssertionFormat(): ApiError { export function unexpectedClientAssertionPayload(): ApiError { return new ApiError({ - detail: `Invalid format for Client assertion`, + detail: "Unexpected client assertion payload", code: "unexpectedClientAssertionPayload", - title: "Invalid format for Client assertion", + title: "Invalid client assertion payload", }); } export function jtiNotFound(): ApiError { return new ApiError({ - detail: `jti not found in client assertion`, + detail: `JTI not found in client assertion`, code: "jtiNotFound", - title: "jti not found", + title: "JTI not found", }); } export function issuedAtNotFound(): ApiError { return new ApiError({ - detail: `iat not found in client assertion`, + detail: `IAT not found in client assertion`, code: "issuedAtNotFound", - title: "iat not found", + title: "IAT not found", }); } export function expNotFound(): ApiError { return new ApiError({ - detail: `exp not found in client assertion`, + detail: `EXP not found in client assertion`, code: "expNotFound", - title: "exp not found", + title: "EXP not found", }); } @@ -155,13 +155,13 @@ export function issuerNotFound(): ApiError { return new ApiError({ detail: `Issuer not found in client assertion`, code: "issuerNotFound", - title: "iss not found", + title: "ISS not found", }); } export function subjectNotFound(): ApiError { return new ApiError({ - detail: `Subject not found in client assertion`, + detail: "Subject not found in client assertion", code: "subjectNotFound", title: "Subject not found", }); @@ -187,9 +187,9 @@ export function invalidPurposeIdClaimFormat( export function kidNotFound(): ApiError { return new ApiError({ - detail: `kid not found in client assertion`, + detail: `KID not found in client assertion`, code: "kidNotFound", - title: "kid not found", + title: "KID not found", }); } @@ -207,7 +207,7 @@ export function tokenExpiredError(): ApiError { return new ApiError({ detail: "Token expired in client assertion signature validation", code: "tokenExpiredError", - title: "Token expired in client assertion signature validation", + title: "Token expired", }); } @@ -215,7 +215,7 @@ export function jsonWebTokenError(): ApiError { return new ApiError({ detail: "Invalid JWT format in client assertion signature validation", code: "jsonWebTokenError", - title: "Invalid JWT format in client assertion signature validation", + title: "Invalid JWT format", }); } @@ -224,8 +224,7 @@ export function notBeforeError(): ApiError { detail: "Current time is before not before time in client assertion signature validation", code: "notBeforeError", - title: - "Current time is before not before time in client assertion signature validation", + title: "Current time is before not before time", }); } From facb19c31376a573e3ef612614a019e4d93c42d6 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 11 Sep 2024 10:25:46 +0200 Subject: [PATCH 156/241] Refactor --- .../client-assertion-validation/src/types.ts | 4 + .../client-assertion-validation/src/utils.ts | 320 ++++++++++++------ .../src/validation.ts | 42 +-- 3 files changed, 243 insertions(+), 123 deletions(-) diff --git a/packages/client-assertion-validation/src/types.ts b/packages/client-assertion-validation/src/types.ts index 6c8055e720..c8cdd93b9a 100644 --- a/packages/client-assertion-validation/src/types.ts +++ b/packages/client-assertion-validation/src/types.ts @@ -63,6 +63,10 @@ export type ValidationResult = | { errors: undefined; data: ClientAssertion } | { errors: Array>; data: undefined }; +export type FlexibleValidationResult = + | { errors: undefined; data: T } + | { errors: Array>; data: undefined }; + // alternatively export type ValidationResult2 = { errors: Array>; diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index 010dd0f313..a0c34d20f7 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -/* eslint-disable functional/immutable-data */ import { authorizationServerApi } from "pagopa-interop-api-clients"; import { decode, @@ -8,10 +6,11 @@ import { TokenExpiredError, verify, } from "jsonwebtoken"; -import { ApiError, PurposeId } from "pagopa-interop-models"; +import { ApiError, PurposeId, unsafeBrandId } from "pagopa-interop-models"; import { ClientAssertion, ConsumerKey, + FlexibleValidationResult, Key, ValidationResult, } from "./types.js"; @@ -47,120 +46,232 @@ const EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE = "client_credentials"; // To do: e export const validateRequestParameters = ( request: authorizationServerApi.AccessTokenRequest -): Array> => { - const errors: Array> = []; - if (request.client_assertion_type !== EXPECTED_CLIENT_ASSERTION_TYPE) { - // eslint-disable-next-line functional/immutable-data - errors.push(invalidAssertionType(request.client_assertion_type)); +): Array> | undefined => { + const assertionTypeError = + request.client_assertion_type !== EXPECTED_CLIENT_ASSERTION_TYPE + ? invalidAssertionType(request.client_assertion_type) + : undefined; + + const grantTypeError = + request.grant_type !== EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE + ? invalidGrantType(request.grant_type) + : undefined; + + if (!assertionTypeError && !grantTypeError) { + return undefined; + } + return [assertionTypeError, grantTypeError].filter((e) => e !== undefined); +}; + +const validateJti = (jti?: string): FlexibleValidationResult => { + if (!jti) { + return { + errors: [jtiNotFound()], + data: undefined, + }; + } else { + return { + errors: undefined, + data: jti, + }; + } +}; + +const validateIat = (iat?: number): FlexibleValidationResult => { + if (!iat) { + return { + errors: [issuedAtNotFound()], + data: undefined, + }; + } else { + return { + errors: undefined, + data: iat, + }; } - if (request.grant_type !== EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE) { - // eslint-disable-next-line functional/immutable-data - errors.push(invalidGrantType(request.grant_type)); +}; + +const validateExp = (exp?: number): FlexibleValidationResult => { + if (!exp) { + return { + errors: [expNotFound()], + data: undefined, + }; + } else { + return { + errors: undefined, + data: exp, + }; } +}; - return errors; +const validateIss = (iss?: string): FlexibleValidationResult => { + if (!iss) { + return { + errors: [issuerNotFound()], + data: undefined, + }; + } else { + return { + errors: undefined, + data: iss, + }; + } +}; + +const validateSub = ( + sub?: string, + clientId?: string +): FlexibleValidationResult => { + if (!sub) { + return { + errors: [subjectNotFound()], + data: undefined, + }; + } else { + if (clientId && sub !== clientId) { + // Todo add check on clientId as ClientId type + return { + errors: [invalidSubject()], + data: undefined, + }; + } + return { + errors: undefined, + data: sub, + }; + } +}; + +const validatePurposeId = ( + purposeId?: string +): FlexibleValidationResult => { + if (purposeId && !PurposeId.safeParse(purposeId).success) { + return { + errors: [invalidPurposeIdClaimFormat(purposeId)], + data: undefined, + }; + } else { + return { + errors: undefined, + data: purposeId ? unsafeBrandId(purposeId) : undefined, + }; + } +}; + +const validateKid = (kid?: string): FlexibleValidationResult => { + if (!kid) { + return { + errors: [kidNotFound()], + data: undefined, + }; + } else { + return { + errors: undefined, + data: kid, + }; + } }; const validateAudience = ( aud: string | string[] | undefined -): { - audienceErrors: Array>; - validatedAudience: string[]; -} => { +): FlexibleValidationResult => { if (aud === CLIENT_ASSERTION_AUDIENCE) { - return { audienceErrors: [], validatedAudience: [aud] }; + return { errors: undefined, data: [aud] }; } if (!Array.isArray(aud)) { return { - audienceErrors: [invalidAudienceFormat()], - validatedAudience: [], // to do check fallback value [] + errors: [invalidAudienceFormat()], + data: undefined, }; } else { if (!aud.includes(CLIENT_ASSERTION_AUDIENCE)) { - return { audienceErrors: [invalidAudience()], validatedAudience: [] }; // to do check fallback value [] + return { errors: [invalidAudience()], data: undefined }; } - return { audienceErrors: [], validatedAudience: aud }; + return { errors: undefined, data: aud }; } }; export const verifyClientAssertion = ( clientAssertionJws: string, clientId: string | undefined - // eslint-disable-next-line sonarjs/cognitive-complexity ): ValidationResult => { const decoded = decode(clientAssertionJws, { complete: true, json: true }); - const errors: Array> = []; if (!decoded) { - errors.push(invalidClientAssertionFormat()); - return { errors, data: undefined }; - } else { - if (typeof decoded.payload === "string") { - errors.push(unexpectedClientAssertionPayload()); // To do: how to test? - return { errors, data: undefined }; - } else { - if (!decoded.payload.jti) { - errors.push(jtiNotFound()); - } - - if (!decoded.payload.iat) { - errors.push(issuedAtNotFound()); - } - - if (!decoded.payload.exp) { - errors.push(expNotFound()); - } - - if (!decoded.payload.iss) { - errors.push(issuerNotFound()); - } - - if (!decoded.payload.sub) { - errors.push(subjectNotFound()); - } - - if (clientId && decoded.payload.sub !== clientId) { - errors.push(invalidSubject(decoded.payload.sub)); - } - - if ( - decoded.payload.purposeId && - !PurposeId.safeParse(decoded.payload.purposeId).success - ) { - errors.push(invalidPurposeIdClaimFormat(decoded.payload.purposeId)); - } - - if (!decoded.header.kid) { - errors.push(kidNotFound()); - } - - const { audienceErrors, validatedAudience } = validateAudience( - decoded.payload.aud - ); - errors.push(...audienceErrors); - - const result: ClientAssertion = { - header: { - kid: decoded.header.kid!, - alg: decoded.header.alg, - }, - payload: { - sub: decoded.payload.sub!, - purposeId: decoded.payload.purposeId, - jti: decoded.payload.jti!, - iat: decoded.payload.iat!, - iss: decoded.payload.iss!, - aud: validatedAudience, - exp: decoded.payload.exp!, // TODO Check unit of measure - }, - }; + return { errors: [invalidClientAssertionFormat()], data: undefined }; + } - return errors.length === 0 - ? { errors: undefined, data: result } - : { errors, data: undefined }; - } + if (typeof decoded.payload === "string") { + return { errors: [unexpectedClientAssertionPayload()], data: undefined }; + } + + const { errors: jtiErrors, data: validatedJti } = validateJti( + decoded.payload.jti + ); + const { errors: iatErrors, data: validatedIat } = validateIat( + decoded.payload.iat + ); + const { errors: expErrors, data: validatedExp } = validateExp( + decoded.payload.exp + ); + const { errors: issErrors, data: validatedIss } = validateIss( + decoded.payload.iss + ); + const { errors: subErrors, data: validatedSub } = validateSub( + decoded.payload.iss, + clientId + ); + const { errors: purposeIdErrors, data: validatedPurposeId } = + validatePurposeId(decoded.payload.purposeId); + const { errors: kidErrors, data: validatedKid } = validateKid( + decoded.payload.kid + ); + const { errors: audErrors, data: validatedAud } = validateAudience( + decoded.payload.aud + ); + + if ( + !jtiErrors && + !iatErrors && + !expErrors && + !issErrors && + !subErrors && + !purposeIdErrors && + !kidErrors && + !audErrors + ) { + const result: ClientAssertion = { + header: { + kid: validatedKid, + alg: decoded.header.alg, + }, + payload: { + sub: validatedSub, + purposeId: validatedPurposeId, + jti: validatedJti, + iat: validatedIat, + iss: validatedIss, + aud: validatedAud, + exp: validatedExp, // TODO Check unit of measure + }, + }; + return { errors: undefined, data: result }; } + return { + errors: [ + ...(jtiErrors || []), + ...(iatErrors || []), + ...(expErrors || []), + ...(issErrors || []), + ...(subErrors || []), + ...(purposeIdErrors || []), + ...(kidErrors || []), + ...(audErrors || []), + ], + data: undefined, + }; }; export const b64Decode = (str: string): string => @@ -169,7 +280,7 @@ export const b64Decode = (str: string): string => export const verifyClientAssertionSignature = ( clientAssertionJws: string, key: Key -): Array> => { +): Array> | undefined => { // todo: should this return a JwtPayload? Probably not try { const result = verify(clientAssertionJws, b64Decode(key.publicKey), { @@ -180,19 +291,23 @@ export const verifyClientAssertionSignature = ( if (typeof result === "string") { return [invalidClientAssertionSignatureType(typeof result)]; } else { - return []; + return undefined; } } catch (error: unknown) { if (error instanceof TokenExpiredError) { + // eslint-disable-next-line no-console console.log("TokenExpiredError"); return [tokenExpiredError()]; } else if (error instanceof JsonWebTokenError) { + // eslint-disable-next-line no-console console.log("JsonWebTokenError"); return [jsonWebTokenError()]; } else if (error instanceof NotBeforeError) { + // eslint-disable-next-line no-console console.log("NotBeforeError"); return [notBeforeError()]; } else { + // eslint-disable-next-line no-console console.log("unknown error"); return [clientAssertionSignatureVerificationFailure()]; } @@ -203,15 +318,16 @@ export const assertValidPlatformState = ( key: ConsumerKey ): Array> => { // To do: is it ok to have these check throwing errors? So that they can be read if needed (instead of just getting false) - const errors: Array> = []; - if (key.agreementState !== "ACTIVE") { - errors.push(inactiveAgreement()); - } - if (key.descriptorState !== "ACTIVE") { - errors.push(inactiveEService()); - } - if (key.purposeState !== "ACTIVE") { - errors.push(inactivePurpose()); - } - return errors; + const agreementError = + key.agreementState !== "ACTIVE" ? inactiveAgreement() : undefined; + + const descriptorError = + key.descriptorState !== "ACTIVE" ? inactiveEService() : undefined; + + const purposeError = + key.purposeState !== "ACTIVE" ? inactivePurpose() : undefined; + + return [agreementError, descriptorError, purposeError].filter( + (e) => e !== undefined + ); }; diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index 8e47c610a4..f81b8ac20d 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -1,19 +1,18 @@ import { authorizationServerApi } from "pagopa-interop-api-clients"; import { match } from "ts-pattern"; -import { ApiError, clientKind } from "pagopa-interop-models"; +import { clientKind } from "pagopa-interop-models"; import { verifyClientAssertionSignature, validateRequestParameters, assertValidPlatformState, verifyClientAssertion, } from "./utils.js"; -import { ApiKey, ConsumerKey } from "./types.js"; -import { ErrorCodes } from "./errors.js"; +import { ApiKey, ConsumerKey, ValidationResult } from "./types.js"; export const assertValidClientAssertion = async ( request: authorizationServerApi.AccessTokenRequest, key: ConsumerKey | ApiKey // Todo use just Key? -): Promise>> => { +): Promise => { const parametersErrors = validateRequestParameters(request); const { errors: clientAssertionVerificationErrors, data: jwt } = @@ -25,27 +24,33 @@ export const assertValidClientAssertion = async ( ); // todo exit here if there are any errors so far? + if ( + parametersErrors || + clientAssertionVerificationErrors || + clientAssertionSignatureErrors + ) { + return { + data: undefined, + errors: [ + ...(parametersErrors || []), + ...(clientAssertionVerificationErrors || []), + ...(clientAssertionSignatureErrors || []), + ], + }; + } if (ApiKey.safeParse(key).success) { - return [ - ...parametersErrors, - ...(clientAssertionVerificationErrors || []), - ...clientAssertionSignatureErrors, - ]; + return { data: jwt, errors: undefined }; } + // todo complete the part below return match(key.clientKind) .with(clientKind.api, () => { const parsingErrors = !ApiKey.safeParse(key).success ? [Error("parsing")] : []; - return [ - ...parsingErrors, - ...parametersErrors, - ...(clientAssertionVerificationErrors || []), - ...clientAssertionSignatureErrors, - ]; + return [...parsingErrors]; }) .with(clientKind.consumer, () => { const errors: Error[] = []; @@ -58,12 +63,7 @@ export const assertValidClientAssertion = async ( // eslint-disable-next-line functional/immutable-data errors.push(Error("parsing")); } - return [ - ...errors, - ...parametersErrors, - ...(clientAssertionVerificationErrors || []), - ...clientAssertionSignatureErrors, - ]; + return [...errors]; }) .exhaustive(); }; From 59f0aa59047d81b7804fcad6eb95b62b902b0799 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 11 Sep 2024 11:43:20 +0200 Subject: [PATCH 157/241] Update comment --- packages/client-assertion-validation/src/errors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client-assertion-validation/src/errors.ts b/packages/client-assertion-validation/src/errors.ts index 173a15cc05..0bc933e7c4 100644 --- a/packages/client-assertion-validation/src/errors.ts +++ b/packages/client-assertion-validation/src/errors.ts @@ -36,7 +36,7 @@ export type ErrorCodes = keyof typeof errorCodes; // - ClientAssertionParseFailed -> already handled in invalidClientAssertionFormat // - ClientAssertionInvalidClaims -> should be covered by individual checks // - InvalidSubjectFormat -> check on uuid of subject claim -// - InvalidPurposeIdFormat -> check on uuid of purposeId claim +// - InvalidPurposeIdFormat -> check on uuid of purposeId claim (already covered by invalidPurposeIdClaimFormat?) // - DigestClaimNotFound -> check if custom claim digest exists // - InvalidDigestClaims -> check if digest has {alg, value} // - InvalidDigestFormat -> probably overlapping with previous From c1c4c3e76a4ed174093f3d272ef169709687a8a4 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 11 Sep 2024 11:43:35 +0200 Subject: [PATCH 158/241] Fix checks --- packages/client-assertion-validation/src/utils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index a0c34d20f7..10980e853b 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -132,7 +132,7 @@ const validateSub = ( if (clientId && sub !== clientId) { // Todo add check on clientId as ClientId type return { - errors: [invalidSubject()], + errors: [invalidSubject(sub)], data: undefined, }; } @@ -220,13 +220,13 @@ export const verifyClientAssertion = ( decoded.payload.iss ); const { errors: subErrors, data: validatedSub } = validateSub( - decoded.payload.iss, + decoded.payload.sub, clientId ); const { errors: purposeIdErrors, data: validatedPurposeId } = validatePurposeId(decoded.payload.purposeId); const { errors: kidErrors, data: validatedKid } = validateKid( - decoded.payload.kid + decoded.header.kid ); const { errors: audErrors, data: validatedAud } = validateAudience( decoded.payload.aud From b72c5dc72e8ec2ed58c9bff0095a8331dd67b2ef Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 11 Sep 2024 11:43:50 +0200 Subject: [PATCH 159/241] Update test utils --- .../client-assertion-validation/test/utils.ts | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/packages/client-assertion-validation/test/utils.ts b/packages/client-assertion-validation/test/utils.ts index 532bf538e9..c9c6cebee9 100644 --- a/packages/client-assertion-validation/test/utils.ts +++ b/packages/client-assertion-validation/test/utils.ts @@ -1,17 +1,25 @@ import crypto from "crypto"; import * as jwt from "jsonwebtoken"; -import { ClientId, generateId } from "pagopa-interop-models"; -import { ClientAssertionPayload } from ".././src/types"; +import { + ClientId, + generateId, + itemState, + PurposeId, + TenantId, +} from "pagopa-interop-models"; +import { ClientAssertionHeader, ConsumerKey } from ".././src/types"; export const getMockClientAssertion = ({ + customHeader, payload, customClaims, }: { - payload: Partial; + customHeader: Partial; + payload: Partial; customClaims: { [k: string]: unknown }; }): string => { const clientId = generateId(); - const defaultPayload = { + const defaultPayload: jwt.JwtPayload = { iss: clientId, sub: clientId, aud: ["test.interop.pagopa.it"], @@ -33,9 +41,25 @@ export const getMockClientAssertion = ({ const options: jwt.SignOptions = { header: { + ...customHeader, kid: generateId(), alg: "RS256", }, }; return jwt.sign(actualPayload, keySet.privateKey, options); }; + +export const getMockConsumerKey = (): ConsumerKey => ({ + GSIPK_clientId: generateId(), + consumerId: generateId(), + kidWithPurposeId: "", + publicKey: "todo", + algorithm: "RS256", + clientKind: "Consumer", + GSIPK_purposeId: generateId(), + purposeState: itemState.active, + agreementId: generateId(), + agreementState: itemState.active, + eServiceId: generateId(), + descriptorState: itemState.active, +}); From 9ba64f68c8460588f2c6777d590fdef8202a186e Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 11 Sep 2024 11:44:03 +0200 Subject: [PATCH 160/241] Draft tests --- .../test/sample.test.ts | 269 ++++++++++++++++-- 1 file changed, 240 insertions(+), 29 deletions(-) diff --git a/packages/client-assertion-validation/test/sample.test.ts b/packages/client-assertion-validation/test/sample.test.ts index d6aa64798a..8722d5f535 100644 --- a/packages/client-assertion-validation/test/sample.test.ts +++ b/packages/client-assertion-validation/test/sample.test.ts @@ -1,11 +1,17 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import crypto from "crypto"; import { describe, expect, it } from "vitest"; -import { ClientId, generateId } from "pagopa-interop-models"; +import { ClientId, generateId, itemState } from "pagopa-interop-models"; import * as jwt from "jsonwebtoken"; -import { verifyClientAssertion } from "../src/utils.js"; +import { + assertValidPlatformState, + verifyClientAssertion, +} from "../src/utils.js"; import { expNotFound, + inactiveAgreement, + inactiveEService, + inactivePurpose, invalidAudience, invalidAudienceFormat, invalidClientAssertionFormat, @@ -17,41 +23,29 @@ import { subjectNotFound, unexpectedClientAssertionPayload, } from "../src/errors.js"; -import { getMockClientAssertion } from "./utils.js"; +import { ConsumerKey } from "../src/types.js"; +import { getMockClientAssertion, getMockConsumerKey } from "./utils.js"; describe("test", () => { describe("validateRequestParameters", () => { it("invalidAssertionType", () => { + // todo expect(1).toBe(1); }); it("invalidGrantType", () => { + // todo expect(1).toBe(1); }); }); describe("verifyClientAssertion", () => { it("invalidAudienceFormat", () => { - const keySet = crypto.generateKeyPairSync("rsa", { - modulusLength: 2048, + const a = getMockClientAssertion({ + customHeader: {}, + payload: { aud: "random" }, + customClaims: { key: 1 }, }); - - const payload = { - iss: generateId(), - sub: generateId(), - aud: "not an array", - exp: 60, - jti: generateId(), - iat: 5, - }; - - const options: jwt.SignOptions = { - header: { - kid: generateId(), - alg: "RS256", - }, - }; - const jws = jwt.sign(payload, keySet.privateKey, options); - const { errors } = verifyClientAssertion(jws, undefined); + const { errors } = verifyClientAssertion(a, undefined); expect(errors).toBeDefined(); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(invalidAudienceFormat()); @@ -59,6 +53,7 @@ describe("test", () => { it("invalidAudience", () => { const a = getMockClientAssertion({ + customHeader: {}, payload: { aud: ["random"] }, customClaims: { key: 1 }, }); @@ -115,6 +110,7 @@ describe("test", () => { it("jtiNotFound", () => { const a = getMockClientAssertion({ + customHeader: {}, payload: { jti: undefined }, customClaims: { key: 1 }, }); @@ -128,6 +124,7 @@ describe("test", () => { // to do: how to test? The sign function automatically adds iat if not present const a = getMockClientAssertion({ + customHeader: {}, payload: {}, customClaims: { key: 1 }, }); @@ -167,6 +164,7 @@ describe("test", () => { it("issuerNotFound", () => { const jws = getMockClientAssertion({ + customHeader: {}, payload: { iss: undefined }, customClaims: {}, }); @@ -178,6 +176,7 @@ describe("test", () => { it("subjectNotFound", () => { const jws = getMockClientAssertion({ + customHeader: {}, payload: { sub: undefined }, customClaims: {}, }); @@ -190,6 +189,7 @@ describe("test", () => { it("invalidSubject", () => { const subject = generateId(); const jws = getMockClientAssertion({ + customHeader: {}, payload: { sub: subject }, customClaims: {}, }); @@ -199,9 +199,23 @@ describe("test", () => { expect(errors![0]).toEqual(invalidSubject(subject)); }); + it("invalidSubjectFormat", () => { + const subject = "not a client id"; + const jws = getMockClientAssertion({ + customHeader: {}, + payload: { sub: subject }, + customClaims: {}, + }); + const { errors } = verifyClientAssertion(jws, generateId()); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors![0]).toEqual(invalidSubjectFormat(subject)); + }); + it("invalidPurposeIdClaimFormat", () => { const notPurposeId = "not a purpose id"; const jws = getMockClientAssertion({ + customHeader: {}, payload: {}, customClaims: { purposeId: notPurposeId }, }); @@ -210,35 +224,232 @@ describe("test", () => { expect(errors).toHaveLength(1); expect(errors![0]).toEqual(invalidPurposeIdClaimFormat(notPurposeId)); }); + + it("invalidClientIdFormat", () => { + const notClientId = "not a client id"; + const jws = getMockClientAssertion({ + customHeader: {}, + payload: {}, + customClaims: {}, + }); + const { errors } = verifyClientAssertion(jws, notClientId); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors![0]).toEqual(invalidClientIdFormat(notClientId)); + }); + + it("digestClaimNotFound", () => { + const jws = getMockClientAssertion({ + customHeader: {}, + payload: {}, + customClaims: {}, + }); + const { errors } = verifyClientAssertion(jws, undefined); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors![0]).toEqual(digestClaimNotFound()); + }); + + it("invalidDigestClaims", () => { + const jws = getMockClientAssertion({ + customHeader: {}, + payload: {}, + customClaims: { digest: { alg: "alg", invalidProp: true } }, + }); + const { errors } = verifyClientAssertion(jws, undefined); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors![0]).toEqual(invalidDigestClaims()); + }); + + it("invalidHashLength", () => { + const jws = getMockClientAssertion({ + customHeader: {}, + payload: {}, + customClaims: { + digest: { alg: "alg", value: "todo string of wrong length" }, + }, + }); + const { errors } = verifyClientAssertion(jws, undefined); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors![0]).toEqual(invalidHashLength()); + }); + + it("InvalidHashAlgorithm", () => { + const jws = getMockClientAssertion({ + customHeader: {}, + payload: {}, + customClaims: { + digest: { alg: "wrong alg", value: "todo string of correct length" }, + }, + }); + const { errors } = verifyClientAssertion(jws, undefined); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors![0]).toEqual(invalidHashAlgorithm()); + }); + + it("AlgorithmNotFound", () => { + const jws = getMockClientAssertion({ + customHeader: { alg: undefined }, + payload: {}, + customClaims: { + digest: { alg: "alg", value: "todo string of correct length" }, + }, + }); + const { errors } = verifyClientAssertion(jws, undefined); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors![0]).toEqual(algorithmNotFound()); + }); + + it("AlgorithmNotAllowed", () => { + const jws = getMockClientAssertion({ + customHeader: { alg: "todo not allowed alg" }, + payload: {}, + customClaims: { + digest: { alg: "alg", value: "todo string of correct length" }, + }, + }); + const { errors } = verifyClientAssertion(jws, undefined); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors![0]).toEqual(algorithmNotAllowed()); + }); + + it("purposeIdNotProvided", () => { + const jws = getMockClientAssertion({ + customHeader: {}, + payload: { purposeId: undefined }, + customClaims: { + digest: { alg: "alg", value: "todo string of correct length" }, + }, + }); + const { errors } = verifyClientAssertion(jws, undefined); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors![0]).toEqual(algorithmNotAllowed()); + }); + + it("PurposeNotFound", () => { + // todo + expect(1).toBe(1); + }); + + it("InvalidKidFormat", () => { + const jws = getMockClientAssertion({ + customHeader: { kid: "not-a-valid-kid" }, + payload: {}, + customClaims: { + digest: { alg: "alg", value: "todo string of correct length" }, + }, + }); + const { errors } = verifyClientAssertion(jws, undefined); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors![0]).toEqual(invalidKidFormat()); + }); }); describe("verifyClientAssertionSignature", () => { it("invalidClientAssertionSignatureType", () => { + // todo: find out when the jwonwebtoken.verify functin returns a string expect(1).toBe(1); }); it("tokenExpiredError", () => { - expect(1).toBe(1); + const date1 = new Date(); + const sixHoursAgo = new Date(date1.setHours(date1.getHours() - 6)); + const date2 = new Date(); + const threeHourAgo = new Date(date2.setHours(date2.getHours() - 3)); + + const jws = getMockClientAssertion({ + customHeader: { kid: "not-a-valid-kid" }, + payload: { + iat: sixHoursAgo.getSeconds(), + exp: threeHourAgo.getSeconds(), + }, + customClaims: { + digest: { alg: "alg", value: "todo string of correct length" }, + }, + }); + const { errors } = verifyClientAssertion(jws, undefined); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors![0]).toEqual(tokenExpiredError()); }); it("jsonWebTokenError", () => { + // todo expect(1).toBe(1); }); it("notBeforeError", () => { - expect(1).toBe(1); + const date1 = new Date(); + const threeHoursAgo = new Date(date1.setHours(date1.getHours() - 3)); + + const date2 = new Date(); + const threeHoursLater = new Date(date2.setHours(date2.getHours() + 3)); + + const date3 = new Date(); + const sixHoursLater = new Date(date3.setHours(date3.getHours() + 6)); + + const jws = getMockClientAssertion({ + customHeader: { kid: "not-a-valid-kid" }, + payload: { + iat: threeHoursAgo.getSeconds(), + exp: sixHoursLater.getSeconds(), + nbf: threeHoursLater.getSeconds(), + }, + customClaims: { + digest: { alg: "alg", value: "todo string of correct length" }, + }, + }); + const { errors } = verifyClientAssertion(jws, undefined); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors![0]).toEqual(notBeforeError()); }); it("clientAssertionSignatureVerificationFailure", () => { + // todo expect(1).toBe(1); }); }); describe("assertValidPlatformStates", () => { it("inactiveAgreement", () => { - expect(1).toBe(1); + const mockKey: ConsumerKey = { + ...getMockConsumerKey(), + agreementState: itemState.inactive, + }; + assertValidPlatformState(mockKey); + const errors = assertValidPlatformState(mockKey); + + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors[0]).toEqual(inactiveAgreement()); }); - it("inactiveEservice", () => { - expect(1).toBe(1); + it("inactiveAgreement", () => { + const mockKey: ConsumerKey = { + ...getMockConsumerKey(), + descriptorState: itemState.inactive, + }; + assertValidPlatformState(mockKey); + const errors = assertValidPlatformState(mockKey); + + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors[0]).toEqual(inactiveEService()); }); it("inactivePurpose", () => { - expect(1).toBe(1); + const mockKey: ConsumerKey = { + ...getMockConsumerKey(), + purposeState: itemState.inactive, + }; + assertValidPlatformState(mockKey); + const errors = assertValidPlatformState(mockKey); + + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors[0]).toEqual(inactivePurpose()); }); }); }); From feddfe661e8eac6fade7277d532b9da0f8481bf9 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Wed, 11 Sep 2024 14:44:28 +0200 Subject: [PATCH 161/241] Add errors --- .../client-assertion-validation/src/errors.ts | 110 +++++++++++++-- .../client-assertion-validation/src/types.ts | 7 + .../client-assertion-validation/src/utils.ts | 125 ++++++++++++++++-- 3 files changed, 222 insertions(+), 20 deletions(-) diff --git a/packages/client-assertion-validation/src/errors.ts b/packages/client-assertion-validation/src/errors.ts index 0bc933e7c4..29c24e2efb 100644 --- a/packages/client-assertion-validation/src/errors.ts +++ b/packages/client-assertion-validation/src/errors.ts @@ -25,6 +25,16 @@ export const errorCodes = { inactivePurpose: "0022", inactiveAgreement: "0023", inactiveEService: "0024", + invalidClientIdFormat: "0025", + invalidSubjectFormat: "0026", + digestClaimNotFound: "0027", + invalidDigestFormat: "0028", + invalidHashLength: "0029", + invalidHashAlgorithm: "0030", + algorithmNotFound: "0031", + algorithmNotAllowed: "0032", + purposeIdNotProvided: "0033", + invalidKidFormat: "0034", }; export type ErrorCodes = keyof typeof errorCodes; @@ -33,22 +43,22 @@ export type ErrorCodes = keyof typeof errorCodes; // TODO: missing errors: // - InvalidClientIdFormat -> check on uuid -// - ClientAssertionParseFailed -> already handled in invalidClientAssertionFormat -// - ClientAssertionInvalidClaims -> should be covered by individual checks +// - ClientAssertionParseFailed -> already handled in invalidClientAssertionFormat X +// - ClientAssertionInvalidClaims -> should be covered by individual checks ? // - InvalidSubjectFormat -> check on uuid of subject claim // - InvalidPurposeIdFormat -> check on uuid of purposeId claim (already covered by invalidPurposeIdClaimFormat?) // - DigestClaimNotFound -> check if custom claim digest exists -// - InvalidDigestClaims -> check if digest has {alg, value} -// - InvalidDigestFormat -> probably overlapping with previous +// - InvalidDigestClaims -> check if digest has only {alg, value} keys X we aren't discriminating from safeParse output +// - InvalidDigestFormat -> probably overlapping with previous. Check if digest is a JSON or a map ? // TODO: check if map works with safeParse // - InvalidHashLength -> check on the length of digest.value (digest is a custom claim) // - InvalidHashAlgorithm -> check if digest.alg is sha256 // - AlgorithmNotFound -> check if header.alg is present -// - AlgorithmNotAllowed -> check if (header.alg === RSA) -// - PublicKeyParseFailed -> out of scope for this module -// - ClientAssertionVerificationError -> maybe too generic -// - InvalidClientAssertionSignature -> maybe already covered by existing cases -// - PurposeIdNotProvided -> based on entry type (Api client doesn't need purposeId) -// - PurposeNotFound -> related to previous, check if there is a purpose entry for that purposeId (in platform states) +// - AlgorithmNotAllowed -> check if (header.alg === RS256) +// - PublicKeyParseFailed -> out of scope for this module X +// - ClientAssertionVerificationError -> maybe too generic X +// - InvalidClientAssertionSignature -> maybe already covered by existing cases X +// - PurposeIdNotProvided -> based on entry type (Api client doesn't need purposeId) // TODO: where to put this error? +// - PurposeNotFound -> related to previous, check if there is a purpose entry for that purposeId (in platform states) needed in this package? // - InvalidKidFormat -> Verify that kid does not contain special characters export function clientAssertionValidationFailure( @@ -251,3 +261,83 @@ export function inactiveAgreement(): ApiError { title: "Agreement is not active", }); } + +export function invalidClientIdFormat(clientId: string): ApiError { + return new ApiError({ + detail: `Client id ${clientId} is not a valid UUID`, + code: "invalidClientIdFormat", + title: "Invalid client id format", + }); +} + +export function invalidSubjectFormat(subject: string): ApiError { + return new ApiError({ + detail: `Subject claim ${subject} is not a valid UUID`, + code: "invalidSubjectFormat", + title: "Invalid subject format", + }); +} + +export function digestClaimNotFound(): ApiError { + return new ApiError({ + detail: `Digest claim not found`, + code: "digestClaimNotFound", + title: "Digest claim not found", + }); +} + +export function invalidDigestFormat(): ApiError { + return new ApiError({ + detail: "Invalid format for digest claim", + code: "invalidDigestFormat", + title: "Invalid digest format", + }); +} + +export function invalidHashLength(alg: string): ApiError { + return new ApiError({ + detail: `Invalid hash length for algorithm ${alg}`, + code: "invalidHashLength", + title: "Invalid hash length", + }); +} + +export function invalidHashAlgorithm(): ApiError { + return new ApiError({ + detail: "Invalid hash algorithm", + code: "invalidHashAlgorithm", + title: "Invalid hash algorithm", + }); +} + +export function algorithmNotFound(): ApiError { + return new ApiError({ + detail: "ALG not found in client assertion", + code: "algorithmNotFound", + title: "ALG not found", + }); +} + +export function algorithmNotAllowed(algorithm: string): ApiError { + return new ApiError({ + detail: `Algorithm ${algorithm} is not allowed`, + code: "algorithmNotAllowed", + title: "ALG not allowed", + }); +} + +export function purposeIdNotProvided(): ApiError { + return new ApiError({ + detail: "Claim purposeId does not exist in this assertion", + code: "purposeIdNotProvided", + title: "Purpose Id not provided", + }); +} + +export function invalidKidFormat(): ApiError { + return new ApiError({ + detail: "Unexpected format for kid", + code: "invalidKidFormat", + title: "Invalid KID format", + }); +} diff --git a/packages/client-assertion-validation/src/types.ts b/packages/client-assertion-validation/src/types.ts index c8cdd93b9a..2672552bf8 100644 --- a/packages/client-assertion-validation/src/types.ts +++ b/packages/client-assertion-validation/src/types.ts @@ -11,6 +11,12 @@ import { import { z } from "zod"; import { ErrorCodes } from "./errors.js"; +export const ClientAssertionDigest = z.object({ + alg: z.string(), + value: z.string(), +}); +export type ClientAssertionDigest = z.infer; + export const ClientAssertionHeader = z.object({ kid: z.string(), alg: z.string(), // TODO Enum, which values? @@ -24,6 +30,7 @@ export const ClientAssertionPayload = z.object({ iss: z.string(), aud: z.array(z.string()), exp: z.number(), + digest: ClientAssertionDigest, purposeId: PurposeId.optional(), }); export type ClientAssertionPayload = z.infer; diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index 10980e853b..320388a751 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -6,9 +6,15 @@ import { TokenExpiredError, verify, } from "jsonwebtoken"; -import { ApiError, PurposeId, unsafeBrandId } from "pagopa-interop-models"; +import { + ApiError, + ClientId, + PurposeId, + unsafeBrandId, +} from "pagopa-interop-models"; import { ClientAssertion, + ClientAssertionDigest, ConsumerKey, FlexibleValidationResult, Key, @@ -38,11 +44,22 @@ import { notBeforeError, clientAssertionSignatureVerificationFailure, invalidClientAssertionSignatureType, + invalidClientIdFormat, + invalidSubjectFormat, + algorithmNotFound, + algorithmNotAllowed, + digestClaimNotFound, + invalidDigestFormat, + invalidHashLength, + invalidHashAlgorithm, + invalidKidFormat, } from "./errors.js"; const CLIENT_ASSERTION_AUDIENCE = "test.interop.pagopa.it"; // To do: env? const EXPECTED_CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; // To do: env? const EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE = "client_credentials"; // To do: env? +const ALLOWED_ALGORITHM = "RS256"; +const ALLOWED_DIGEST_ALGORITHM = "SHA256"; export const validateRequestParameters = ( request: authorizationServerApi.AccessTokenRequest @@ -129,12 +146,26 @@ const validateSub = ( data: undefined, }; } else { - if (clientId && sub !== clientId) { - // Todo add check on clientId as ClientId type - return { - errors: [invalidSubject(sub)], - data: undefined, - }; + if (clientId) { + const clientIdError = !ClientId.safeParse(clientId).success + ? invalidClientIdFormat(clientId) + : undefined; + const invalidSubFormatError = !ClientId.safeParse(sub).success + ? invalidSubjectFormat(sub) + : undefined; + // TODO: clientId undefined OK? + const invalidSubError = + sub !== clientId ? invalidSubject(sub) : undefined; + if (clientIdError || invalidSubFormatError || invalidSubError) { + return { + errors: [ + clientIdError, + invalidSubFormatError, + invalidSubError, + ].filter((e) => e !== undefined), + data: undefined, + }; + } } return { errors: undefined, @@ -165,12 +196,18 @@ const validateKid = (kid?: string): FlexibleValidationResult => { errors: [kidNotFound()], data: undefined, }; - } else { + } + const alphanumericRegex = new RegExp("^[a-zA-Z0-9]+$"); + if (alphanumericRegex.test(kid)) { return { errors: undefined, data: kid, }; } + return { + errors: [invalidKidFormat()], + data: undefined, + }; }; const validateAudience = ( @@ -193,6 +230,63 @@ const validateAudience = ( } }; +const validateAlgorithm = (alg?: string): FlexibleValidationResult => { + if (!alg) { + return { + errors: [algorithmNotFound()], + data: undefined, + }; + } + if (alg !== ALLOWED_ALGORITHM) { + return { + errors: undefined, + data: alg, + }; + } + return { + errors: [algorithmNotAllowed(alg)], + data: undefined, + }; +}; + +const validateDigest = ( + digest?: object +): FlexibleValidationResult => { + if (!digest) { + return { + errors: [digestClaimNotFound()], + data: undefined, + }; + } + const result = ClientAssertionDigest.safeParse(digest); + if (!result.success) { + return { + errors: [invalidDigestFormat()], + data: undefined, + }; + } + const validatedDigest = result.data; + const digestLengthError = + validatedDigest.value.length !== 64 + ? invalidHashLength(validatedDigest.alg) + : undefined; + const digestAlgError = + validatedDigest.alg !== ALLOWED_DIGEST_ALGORITHM + ? invalidHashAlgorithm() + : undefined; + if (!digestLengthError && !digestAlgError) { + return { + errors: undefined, + data: result.data, + }; + } + return { + errors: [digestLengthError, digestAlgError].filter((e) => e !== undefined), + data: undefined, + }; +}; + +// eslint-disable-next-line complexity export const verifyClientAssertion = ( clientAssertionJws: string, clientId: string | undefined @@ -231,6 +325,12 @@ export const verifyClientAssertion = ( const { errors: audErrors, data: validatedAud } = validateAudience( decoded.payload.aud ); + const { errors: algErrors, data: validatedAlg } = validateAlgorithm( + decoded.header.alg + ); + const { errors: digestErrors, data: validatedDigest } = validateDigest( + decoded.payload.digest + ); if ( !jtiErrors && @@ -240,12 +340,14 @@ export const verifyClientAssertion = ( !subErrors && !purposeIdErrors && !kidErrors && - !audErrors + !audErrors && + !algErrors && + !digestErrors ) { const result: ClientAssertion = { header: { kid: validatedKid, - alg: decoded.header.alg, + alg: validatedAlg, }, payload: { sub: validatedSub, @@ -255,6 +357,7 @@ export const verifyClientAssertion = ( iss: validatedIss, aud: validatedAud, exp: validatedExp, // TODO Check unit of measure + digest: validatedDigest, }, }; return { errors: undefined, data: result }; @@ -269,6 +372,8 @@ export const verifyClientAssertion = ( ...(purposeIdErrors || []), ...(kidErrors || []), ...(audErrors || []), + ...(algErrors || []), + ...(digestErrors || []), ], data: undefined, }; From 659d4d1c1b1bee72d4025f8ad19ca8b05ed64216 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Wed, 11 Sep 2024 15:18:20 +0200 Subject: [PATCH 162/241] Fix algorithm error --- packages/client-assertion-validation/src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index 320388a751..e65bb6c87f 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -237,7 +237,7 @@ const validateAlgorithm = (alg?: string): FlexibleValidationResult => { data: undefined, }; } - if (alg !== ALLOWED_ALGORITHM) { + if (alg === ALLOWED_ALGORITHM) { return { errors: undefined, data: alg, From 8f774eb237604af0bc9c92823f5652e9512e4a26 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 11 Sep 2024 15:45:43 +0200 Subject: [PATCH 163/241] Refactor validateSub --- .../client-assertion-validation/src/utils.ts | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index e65bb6c87f..9e8c84e516 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -145,33 +145,34 @@ const validateSub = ( errors: [subjectNotFound()], data: undefined, }; - } else { - if (clientId) { - const clientIdError = !ClientId.safeParse(clientId).success - ? invalidClientIdFormat(clientId) - : undefined; - const invalidSubFormatError = !ClientId.safeParse(sub).success - ? invalidSubjectFormat(sub) - : undefined; - // TODO: clientId undefined OK? - const invalidSubError = - sub !== clientId ? invalidSubject(sub) : undefined; - if (clientIdError || invalidSubFormatError || invalidSubError) { - return { - errors: [ - clientIdError, - invalidSubFormatError, - invalidSubError, - ].filter((e) => e !== undefined), - data: undefined, - }; - } + } + if (clientId) { + const clientIdError = !ClientId.safeParse(clientId).success + ? invalidClientIdFormat(clientId) + : undefined; + const invalidSubFormatError = !ClientId.safeParse(sub).success + ? invalidSubjectFormat(sub) + : undefined; + if (clientIdError || invalidSubFormatError) { + return { + errors: [clientIdError, invalidSubFormatError].filter( + (e) => e !== undefined + ), + data: undefined, + }; + } + // TODO: clientId undefined OK? + if (sub !== clientId) { + return { + errors: [invalidSubject(sub)], + data: undefined, + }; } - return { - errors: undefined, - data: sub, - }; } + return { + errors: undefined, + data: sub, + }; }; const validatePurposeId = ( From 3991d66dfb64cde6bd97ebe2872a995afeb511dd Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 11 Sep 2024 16:04:24 +0200 Subject: [PATCH 164/241] Fix mock --- packages/client-assertion-validation/test/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client-assertion-validation/test/utils.ts b/packages/client-assertion-validation/test/utils.ts index c9c6cebee9..4e0f7a1a1d 100644 --- a/packages/client-assertion-validation/test/utils.ts +++ b/packages/client-assertion-validation/test/utils.ts @@ -41,9 +41,9 @@ export const getMockClientAssertion = ({ const options: jwt.SignOptions = { header: { - ...customHeader, - kid: generateId(), + kid: "todo", alg: "RS256", + ...customHeader, }, }; return jwt.sign(actualPayload, keySet.privateKey, options); From c97619f6e18f419a896a57bb3bbaed3e8b74ef34 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 11 Sep 2024 16:14:40 +0200 Subject: [PATCH 165/241] Update tests --- .../test/sample.test.ts | 155 ++++++++++++++---- 1 file changed, 124 insertions(+), 31 deletions(-) diff --git a/packages/client-assertion-validation/test/sample.test.ts b/packages/client-assertion-validation/test/sample.test.ts index 8722d5f535..10613dc78d 100644 --- a/packages/client-assertion-validation/test/sample.test.ts +++ b/packages/client-assertion-validation/test/sample.test.ts @@ -1,13 +1,22 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import crypto from "crypto"; import { describe, expect, it } from "vitest"; -import { ClientId, generateId, itemState } from "pagopa-interop-models"; +import { + ApiError, + ClientId, + generateId, + itemState, +} from "pagopa-interop-models"; import * as jwt from "jsonwebtoken"; import { assertValidPlatformState, verifyClientAssertion, } from "../src/utils.js"; import { + algorithmNotAllowed, + algorithmNotFound, + digestClaimNotFound, + ErrorCodes, expNotFound, inactiveAgreement, inactiveEService, @@ -15,15 +24,23 @@ import { invalidAudience, invalidAudienceFormat, invalidClientAssertionFormat, + invalidClientIdFormat, + invalidHashAlgorithm, + invalidHashLength, + invalidKidFormat, invalidPurposeIdClaimFormat, invalidSubject, + invalidSubjectFormat, issuedAtNotFound, issuerNotFound, jtiNotFound, + notBeforeError, subjectNotFound, + tokenExpiredError, unexpectedClientAssertionPayload, } from "../src/errors.js"; import { ConsumerKey } from "../src/types.js"; +import { invalidDigestFormat, purposeIdNotProvided } from "../dist/errors.js"; import { getMockClientAssertion, getMockConsumerKey } from "./utils.js"; describe("test", () => { @@ -38,12 +55,19 @@ describe("test", () => { }); }); + const value64chars = + "1234567890123456789012345678901234567890123456789012345678901234"; describe("verifyClientAssertion", () => { it("invalidAudienceFormat", () => { const a = getMockClientAssertion({ customHeader: {}, payload: { aud: "random" }, - customClaims: { key: 1 }, + customClaims: { + digest: { + alg: "SHA256", + value: value64chars, + }, + }, }); const { errors } = verifyClientAssertion(a, undefined); expect(errors).toBeDefined(); @@ -55,10 +79,17 @@ describe("test", () => { const a = getMockClientAssertion({ customHeader: {}, payload: { aud: ["random"] }, - customClaims: { key: 1 }, + customClaims: { + digest: { + alg: "SHA256", + value: value64chars, + }, + }, }); const { errors } = verifyClientAssertion(a, undefined); expect(errors).toBeDefined(); + printErrors(errors); + expect(errors).toHaveLength(1); expect(errors![0]).toEqual(invalidAudience()); }); @@ -112,10 +143,16 @@ describe("test", () => { const a = getMockClientAssertion({ customHeader: {}, payload: { jti: undefined }, - customClaims: { key: 1 }, + customClaims: { + digest: { + alg: "SHA256", + value: value64chars, + }, + }, }); const { errors } = verifyClientAssertion(a, undefined); expect(errors).toBeDefined(); + printErrors(errors); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(jtiNotFound()); }); @@ -147,17 +184,22 @@ describe("test", () => { aud: ["test.interop.pagopa.it"], jti: generateId(), iat: 5, + digest: { + alg: "SHA256", + value: value64chars, + }, }; const options: jwt.SignOptions = { header: { - kid: generateId(), + kid: "todo", alg: "RS256", }, }; const jws = jwt.sign(payload, keySet.privateKey, options); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); + printErrors(errors); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(expNotFound()); }); @@ -166,7 +208,12 @@ describe("test", () => { const jws = getMockClientAssertion({ customHeader: {}, payload: { iss: undefined }, - customClaims: {}, + customClaims: { + digest: { + alg: "SHA256", + value: value64chars, + }, + }, }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); @@ -178,10 +225,16 @@ describe("test", () => { const jws = getMockClientAssertion({ customHeader: {}, payload: { sub: undefined }, - customClaims: {}, + customClaims: { + digest: { + alg: "SHA256", + value: value64chars, + }, + }, }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); + printErrors(errors); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(subjectNotFound()); }); @@ -191,7 +244,12 @@ describe("test", () => { const jws = getMockClientAssertion({ customHeader: {}, payload: { sub: subject }, - customClaims: {}, + customClaims: { + digest: { + alg: "SHA256", + value: value64chars, + }, + }, }); const { errors } = verifyClientAssertion(jws, generateId()); expect(errors).toBeDefined(); @@ -200,13 +258,19 @@ describe("test", () => { }); it("invalidSubjectFormat", () => { + const clientId: ClientId = generateId(); const subject = "not a client id"; const jws = getMockClientAssertion({ customHeader: {}, payload: { sub: subject }, - customClaims: {}, + customClaims: { + digest: { + alg: "SHA256", + value: value64chars, + }, + }, }); - const { errors } = verifyClientAssertion(jws, generateId()); + const { errors } = verifyClientAssertion(jws, clientId); expect(errors).toBeDefined(); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(invalidSubjectFormat(subject)); @@ -217,7 +281,13 @@ describe("test", () => { const jws = getMockClientAssertion({ customHeader: {}, payload: {}, - customClaims: { purposeId: notPurposeId }, + customClaims: { + purposeId: notPurposeId, + digest: { + alg: "SHA256", + value: value64chars, + }, + }, }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); @@ -230,7 +300,12 @@ describe("test", () => { const jws = getMockClientAssertion({ customHeader: {}, payload: {}, - customClaims: {}, + customClaims: { + digest: { + alg: "SHA256", + value: value64chars, + }, + }, }); const { errors } = verifyClientAssertion(jws, notClientId); expect(errors).toBeDefined(); @@ -259,7 +334,7 @@ describe("test", () => { const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(invalidDigestClaims()); + expect(errors![0]).toEqual(invalidDigestFormat()); }); it("invalidHashLength", () => { @@ -267,13 +342,14 @@ describe("test", () => { customHeader: {}, payload: {}, customClaims: { - digest: { alg: "alg", value: "todo string of wrong length" }, + digest: { alg: "SHA256", value: "todo string of wrong length" }, }, }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); + printErrors(errors); expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(invalidHashLength()); + expect(errors![0]).toEqual(invalidHashLength("SHA256")); }); it("InvalidHashAlgorithm", () => { @@ -281,7 +357,7 @@ describe("test", () => { customHeader: {}, payload: {}, customClaims: { - digest: { alg: "wrong alg", value: "todo string of correct length" }, + digest: { alg: "wrong alg", value: value64chars }, }, }); const { errors } = verifyClientAssertion(jws, undefined); @@ -290,49 +366,54 @@ describe("test", () => { expect(errors![0]).toEqual(invalidHashAlgorithm()); }); - it("AlgorithmNotFound", () => { + it.skip("AlgorithmNotFound", () => { + // todo it seems this can't be tested because we need alg header to sign the mock jwt const jws = getMockClientAssertion({ - customHeader: { alg: undefined }, + customHeader: { alg: "undefined" }, payload: {}, customClaims: { - digest: { alg: "alg", value: "todo string of correct length" }, + digest: { alg: "RS256", value: value64chars }, }, }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); + printErrors(errors); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(algorithmNotFound()); }); it("AlgorithmNotAllowed", () => { + const notAllowedAlg = "RS512"; const jws = getMockClientAssertion({ - customHeader: { alg: "todo not allowed alg" }, + customHeader: { alg: "RS512" }, payload: {}, customClaims: { - digest: { alg: "alg", value: "todo string of correct length" }, + digest: { alg: "SHA256", value: value64chars }, }, }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(algorithmNotAllowed()); + expect(errors![0]).toEqual(algorithmNotAllowed(notAllowedAlg)); }); - it("purposeIdNotProvided", () => { + it.skip("purposeIdNotProvided", () => { + // todo this should be related to the case of consumerKey const jws = getMockClientAssertion({ customHeader: {}, payload: { purposeId: undefined }, customClaims: { - digest: { alg: "alg", value: "todo string of correct length" }, + digest: { alg: "SHA256", value: value64chars }, }, }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); + printErrors(errors); expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(algorithmNotAllowed()); + expect(errors![0]).toEqual(purposeIdNotProvided()); }); - it("PurposeNotFound", () => { + it.skip("PurposeNotFound", () => { // todo expect(1).toBe(1); }); @@ -342,11 +423,12 @@ describe("test", () => { customHeader: { kid: "not-a-valid-kid" }, payload: {}, customClaims: { - digest: { alg: "alg", value: "todo string of correct length" }, + digest: { alg: "SHA256", value: value64chars }, }, }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); + printErrors(errors); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(invalidKidFormat()); }); @@ -358,23 +440,25 @@ describe("test", () => { expect(1).toBe(1); }); it("tokenExpiredError", () => { + // todo why does it fail? const date1 = new Date(); const sixHoursAgo = new Date(date1.setHours(date1.getHours() - 6)); const date2 = new Date(); const threeHourAgo = new Date(date2.setHours(date2.getHours() - 3)); const jws = getMockClientAssertion({ - customHeader: { kid: "not-a-valid-kid" }, + customHeader: {}, payload: { iat: sixHoursAgo.getSeconds(), exp: threeHourAgo.getSeconds(), }, customClaims: { - digest: { alg: "alg", value: "todo string of correct length" }, + digest: { alg: "SHA256", value: value64chars }, }, }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); + printErrors(errors); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(tokenExpiredError()); }); @@ -383,6 +467,8 @@ describe("test", () => { expect(1).toBe(1); }); it("notBeforeError", () => { + // todo why does it fail? + const date1 = new Date(); const threeHoursAgo = new Date(date1.setHours(date1.getHours() - 3)); @@ -393,18 +479,19 @@ describe("test", () => { const sixHoursLater = new Date(date3.setHours(date3.getHours() + 6)); const jws = getMockClientAssertion({ - customHeader: { kid: "not-a-valid-kid" }, + customHeader: {}, payload: { iat: threeHoursAgo.getSeconds(), exp: sixHoursLater.getSeconds(), nbf: threeHoursLater.getSeconds(), }, customClaims: { - digest: { alg: "alg", value: "todo string of correct length" }, + digest: { alg: "SHA256", value: value64chars }, }, }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); + printErrors(errors); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(notBeforeError()); }); @@ -453,3 +540,9 @@ describe("test", () => { }); }); }); + +const printErrors = (errors?: Array>): void => { + if (errors) { + errors.forEach((e) => console.log(e.code, e.detail)); + } +}; From 5a49356e32f07452052856dd8bd3ee0208022f97 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 11 Sep 2024 16:18:45 +0200 Subject: [PATCH 166/241] Remove printed errors --- .../client-assertion-validation/test/sample.test.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/client-assertion-validation/test/sample.test.ts b/packages/client-assertion-validation/test/sample.test.ts index 10613dc78d..9f44a6223c 100644 --- a/packages/client-assertion-validation/test/sample.test.ts +++ b/packages/client-assertion-validation/test/sample.test.ts @@ -88,8 +88,6 @@ describe("test", () => { }); const { errors } = verifyClientAssertion(a, undefined); expect(errors).toBeDefined(); - printErrors(errors); - expect(errors).toHaveLength(1); expect(errors![0]).toEqual(invalidAudience()); }); @@ -152,7 +150,6 @@ describe("test", () => { }); const { errors } = verifyClientAssertion(a, undefined); expect(errors).toBeDefined(); - printErrors(errors); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(jtiNotFound()); }); @@ -199,7 +196,6 @@ describe("test", () => { const jws = jwt.sign(payload, keySet.privateKey, options); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); - printErrors(errors); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(expNotFound()); }); @@ -234,7 +230,6 @@ describe("test", () => { }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); - printErrors(errors); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(subjectNotFound()); }); @@ -347,7 +342,6 @@ describe("test", () => { }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); - printErrors(errors); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(invalidHashLength("SHA256")); }); @@ -377,7 +371,6 @@ describe("test", () => { }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); - printErrors(errors); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(algorithmNotFound()); }); @@ -408,7 +401,6 @@ describe("test", () => { }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); - printErrors(errors); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(purposeIdNotProvided()); }); @@ -428,7 +420,6 @@ describe("test", () => { }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); - printErrors(errors); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(invalidKidFormat()); }); @@ -458,7 +449,6 @@ describe("test", () => { }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); - printErrors(errors); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(tokenExpiredError()); }); @@ -491,7 +481,6 @@ describe("test", () => { }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); - printErrors(errors); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(notBeforeError()); }); From b12a6eac1c3798efaceee82655423fcafcbc08ff Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Wed, 11 Sep 2024 16:55:11 +0200 Subject: [PATCH 167/241] Add errors --- .../client-assertion-validation/src/errors.ts | 4 +- .../client-assertion-validation/src/utils.ts | 12 +--- .../test/sample.test.ts | 57 +++++++++++++------ .../client-assertion-validation/test/utils.ts | 15 +++++ 4 files changed, 59 insertions(+), 29 deletions(-) diff --git a/packages/client-assertion-validation/src/errors.ts b/packages/client-assertion-validation/src/errors.ts index 29c24e2efb..dbadc8551d 100644 --- a/packages/client-assertion-validation/src/errors.ts +++ b/packages/client-assertion-validation/src/errors.ts @@ -221,9 +221,9 @@ export function tokenExpiredError(): ApiError { }); } -export function jsonWebTokenError(): ApiError { +export function jsonWebTokenError(errorMessage: string): ApiError { return new ApiError({ - detail: "Invalid JWT format in client assertion signature validation", + detail: `Invalid JWT format in client assertion signature validation. Reason: ${errorMessage}`, code: "jsonWebTokenError", title: "Invalid JWT format", }); diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index 9e8c84e516..571d2f9959 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -69,6 +69,7 @@ export const validateRequestParameters = ( ? invalidAssertionType(request.client_assertion_type) : undefined; + // TODO: this might be useless because authorizationServerApi.AccessTokenRequest has the string hard coded const grantTypeError = request.grant_type !== EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE ? invalidGrantType(request.grant_type) @@ -401,20 +402,13 @@ export const verifyClientAssertionSignature = ( } } catch (error: unknown) { if (error instanceof TokenExpiredError) { - // eslint-disable-next-line no-console - console.log("TokenExpiredError"); return [tokenExpiredError()]; } else if (error instanceof JsonWebTokenError) { - // eslint-disable-next-line no-console - console.log("JsonWebTokenError"); - return [jsonWebTokenError()]; + // TODO: this might overlap with invalidClientAssertionFormat raised inside invalidClientAssertionFormat + return [jsonWebTokenError(error.message)]; } else if (error instanceof NotBeforeError) { - // eslint-disable-next-line no-console - console.log("NotBeforeError"); return [notBeforeError()]; } else { - // eslint-disable-next-line no-console - console.log("unknown error"); return [clientAssertionSignatureVerificationFailure()]; } } diff --git a/packages/client-assertion-validation/test/sample.test.ts b/packages/client-assertion-validation/test/sample.test.ts index 9f44a6223c..6319f0b44f 100644 --- a/packages/client-assertion-validation/test/sample.test.ts +++ b/packages/client-assertion-validation/test/sample.test.ts @@ -10,7 +10,9 @@ import { import * as jwt from "jsonwebtoken"; import { assertValidPlatformState, + validateRequestParameters, verifyClientAssertion, + verifyClientAssertionSignature, } from "../src/utils.js"; import { algorithmNotAllowed, @@ -21,6 +23,7 @@ import { inactiveAgreement, inactiveEService, inactivePurpose, + invalidAssertionType, invalidAudience, invalidAudienceFormat, invalidClientAssertionFormat, @@ -33,26 +36,48 @@ import { invalidSubjectFormat, issuedAtNotFound, issuerNotFound, + jsonWebTokenError, jtiNotFound, notBeforeError, subjectNotFound, tokenExpiredError, unexpectedClientAssertionPayload, + invalidDigestFormat, + purposeIdNotProvided, } from "../src/errors.js"; import { ConsumerKey } from "../src/types.js"; -import { invalidDigestFormat, purposeIdNotProvided } from "../dist/errors.js"; -import { getMockClientAssertion, getMockConsumerKey } from "./utils.js"; +import { + getMockAccessTokenRequest, + getMockClientAssertion, + getMockConsumerKey, +} from "./utils.js"; describe("test", () => { describe("validateRequestParameters", () => { it("invalidAssertionType", () => { - // todo - expect(1).toBe(1); - }); - it("invalidGrantType", () => { - // todo - expect(1).toBe(1); - }); + const wrongAssertionType = "something-wrong"; + const request = { + ...getMockAccessTokenRequest(), + client_assertion_type: wrongAssertionType, + }; + const errors = validateRequestParameters(request); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors![0]).toEqual(invalidAssertionType(wrongAssertionType)); + }); + // it("invalidGrantType", () => { + // // todo + // const wrongGrantType = "something-wrong"; + // const request = { + // ...getMockAccessTokenRequest(), + // grant_type: wrongGrantType, + // }; + // // TODO: mock already checks grant type + // const errors = validateRequestParameters(request); + // expect(errors).toBeDefined(); + // expect(errors).toHaveLength(1); + // expect(errors![0]).toEqual(invalidGrantType(wrongGrantType)); + // }); }); const value64chars = @@ -163,11 +188,9 @@ describe("test", () => { customClaims: { key: 1 }, }); const { errors } = verifyClientAssertion(a, undefined); - // console.log(errors); expect(errors).toBeDefined(); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(issuedAtNotFound()); - // console.log("error code: ", errors[0].code); }); it("expNotFound", () => { @@ -405,11 +428,6 @@ describe("test", () => { expect(errors![0]).toEqual(purposeIdNotProvided()); }); - it.skip("PurposeNotFound", () => { - // todo - expect(1).toBe(1); - }); - it("InvalidKidFormat", () => { const jws = getMockClientAssertion({ customHeader: { kid: "not-a-valid-kid" }, @@ -453,8 +471,11 @@ describe("test", () => { expect(errors![0]).toEqual(tokenExpiredError()); }); it("jsonWebTokenError", () => { - // todo - expect(1).toBe(1); + const mockKey = getMockConsumerKey(); + const errors = verifyClientAssertionSignature("not-a-valid-jws", mockKey); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors![0].title).toEqual(jsonWebTokenError("").title); }); it("notBeforeError", () => { // todo why does it fail? diff --git a/packages/client-assertion-validation/test/utils.ts b/packages/client-assertion-validation/test/utils.ts index 4e0f7a1a1d..d0d190d11f 100644 --- a/packages/client-assertion-validation/test/utils.ts +++ b/packages/client-assertion-validation/test/utils.ts @@ -1,4 +1,5 @@ import crypto from "crypto"; +import { authorizationServerApi } from "pagopa-interop-api-clients"; import * as jwt from "jsonwebtoken"; import { ClientId, @@ -63,3 +64,17 @@ export const getMockConsumerKey = (): ConsumerKey => ({ eServiceId: generateId(), descriptorState: itemState.active, }); + +export const getMockAccessTokenRequest = + (): authorizationServerApi.AccessTokenRequest => ({ + client_id: generateId(), + // TODO: change to env variable + client_assertion_type: + "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", + client_assertion: getMockClientAssertion({ + customHeader: {}, + payload: {}, + customClaims: {}, + }), + grant_type: "client_credentials", + }); From e2d2de36629696254328ea3951bae40c2c32e8dc Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Thu, 12 Sep 2024 09:49:44 +0200 Subject: [PATCH 168/241] Fix tests --- .../client-assertion-validation/src/utils.ts | 12 ++-- .../test/sample.test.ts | 68 +++++++++++++------ .../client-assertion-validation/test/utils.ts | 46 ++++++++----- 3 files changed, 84 insertions(+), 42 deletions(-) diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index 571d2f9959..6510443ce6 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -381,8 +381,8 @@ export const verifyClientAssertion = ( }; }; -export const b64Decode = (str: string): string => - Buffer.from(str, "base64").toString("binary"); +// export const b64Decode = (str: string): string => +// Buffer.from(str, "base64").toString("binary"); export const verifyClientAssertionSignature = ( clientAssertionJws: string, @@ -390,7 +390,7 @@ export const verifyClientAssertionSignature = ( ): Array> | undefined => { // todo: should this return a JwtPayload? Probably not try { - const result = verify(clientAssertionJws, b64Decode(key.publicKey), { + const result = verify(clientAssertionJws, key.publicKey, { algorithms: [key.algorithm], }); @@ -403,11 +403,11 @@ export const verifyClientAssertionSignature = ( } catch (error: unknown) { if (error instanceof TokenExpiredError) { return [tokenExpiredError()]; - } else if (error instanceof JsonWebTokenError) { - // TODO: this might overlap with invalidClientAssertionFormat raised inside invalidClientAssertionFormat - return [jsonWebTokenError(error.message)]; } else if (error instanceof NotBeforeError) { return [notBeforeError()]; + } else if (error instanceof JsonWebTokenError) { + // TODO: this might overlap with invalidClientAssertionFormat raised inside verifyClientAssertion + return [jsonWebTokenError(error.message)]; } else { return [clientAssertionSignatureVerificationFailure()]; } diff --git a/packages/client-assertion-validation/test/sample.test.ts b/packages/client-assertion-validation/test/sample.test.ts index 6319f0b44f..08c1224ae0 100644 --- a/packages/client-assertion-validation/test/sample.test.ts +++ b/packages/client-assertion-validation/test/sample.test.ts @@ -80,8 +80,8 @@ describe("test", () => { // }); }); - const value64chars = - "1234567890123456789012345678901234567890123456789012345678901234"; + const value64chars = crypto.randomBytes(64).toString("ascii"); + describe("verifyClientAssertion", () => { it("invalidAudienceFormat", () => { const a = getMockClientAssertion({ @@ -450,22 +450,39 @@ describe("test", () => { }); it("tokenExpiredError", () => { // todo why does it fail? - const date1 = new Date(); - const sixHoursAgo = new Date(date1.setHours(date1.getHours() - 6)); - const date2 = new Date(); - const threeHourAgo = new Date(date2.setHours(date2.getHours() - 3)); + const sixHoursAgo = new Date(); + sixHoursAgo.setHours(sixHoursAgo.getHours() - 6); + + const threeHourAgo = new Date(); + threeHourAgo.setHours(threeHourAgo.getHours() - 3); + + const keySet = crypto.generateKeyPairSync("rsa", { + modulusLength: 2048, + }); const jws = getMockClientAssertion({ customHeader: {}, payload: { - iat: sixHoursAgo.getSeconds(), - exp: threeHourAgo.getSeconds(), + iat: sixHoursAgo.getTime() / 1000, + exp: threeHourAgo.getTime() / 1000, }, customClaims: { digest: { alg: "SHA256", value: value64chars }, }, + keySet, }); - const { errors } = verifyClientAssertion(jws, undefined); + // TODO: how to get string from keySet.publicKey + const publicKey = keySet.publicKey + .export({ + type: "pkcs1", + format: "pem", + }) + .toString(); + const mockConsumerKey = { + ...getMockConsumerKey(), + publicKey, + }; + const errors = verifyClientAssertionSignature(jws, mockConsumerKey); expect(errors).toBeDefined(); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(tokenExpiredError()); @@ -480,27 +497,40 @@ describe("test", () => { it("notBeforeError", () => { // todo why does it fail? - const date1 = new Date(); - const threeHoursAgo = new Date(date1.setHours(date1.getHours() - 3)); + const threeHoursAgo = new Date(); + threeHoursAgo.setHours(threeHoursAgo.getHours() - 3); - const date2 = new Date(); - const threeHoursLater = new Date(date2.setHours(date2.getHours() + 3)); + const threeHoursLater = new Date(); + threeHoursLater.setHours(threeHoursLater.getHours() + 3); - const date3 = new Date(); - const sixHoursLater = new Date(date3.setHours(date3.getHours() + 6)); + const sixHoursLater = new Date(); + sixHoursLater.setHours(sixHoursLater.getHours() + 6); + const keySet = crypto.generateKeyPairSync("rsa", { + modulusLength: 2048, + }); const jws = getMockClientAssertion({ customHeader: {}, payload: { - iat: threeHoursAgo.getSeconds(), - exp: sixHoursLater.getSeconds(), - nbf: threeHoursLater.getSeconds(), + iat: threeHoursAgo.getTime() / 1000, + exp: sixHoursLater.getTime() / 1000, + nbf: threeHoursLater.getTime() / 1000, }, customClaims: { digest: { alg: "SHA256", value: value64chars }, }, + keySet, }); - const { errors } = verifyClientAssertion(jws, undefined); + const publicKey = keySet.publicKey.export({ + type: "pkcs1", + format: "pem", + }) as string; + const mockConsumerKey = { + ...getMockConsumerKey(), + publicKey, + }; + + const errors = verifyClientAssertionSignature(jws, mockConsumerKey); expect(errors).toBeDefined(); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(notBeforeError()); diff --git a/packages/client-assertion-validation/test/utils.ts b/packages/client-assertion-validation/test/utils.ts index d0d190d11f..b84209bf01 100644 --- a/packages/client-assertion-validation/test/utils.ts +++ b/packages/client-assertion-validation/test/utils.ts @@ -14,10 +14,12 @@ export const getMockClientAssertion = ({ customHeader, payload, customClaims, + keySet, }: { customHeader: Partial; payload: Partial; customClaims: { [k: string]: unknown }; + keySet?: crypto.KeyPairKeyObjectResult; }): string => { const clientId = generateId(); const defaultPayload: jwt.JwtPayload = { @@ -35,11 +37,6 @@ export const getMockClientAssertion = ({ ...payload, ...customClaims, }; - - const keySet = crypto.generateKeyPairSync("rsa", { - modulusLength: 2048, - }); - const options: jwt.SignOptions = { header: { kid: "todo", @@ -47,6 +44,14 @@ export const getMockClientAssertion = ({ ...customHeader, }, }; + + if (!keySet) { + const keySet = crypto.generateKeyPairSync("rsa", { + modulusLength: 2048, + }); + return jwt.sign(actualPayload, keySet.privateKey, options); + } + return jwt.sign(actualPayload, keySet.privateKey, options); }; @@ -66,15 +71,22 @@ export const getMockConsumerKey = (): ConsumerKey => ({ }); export const getMockAccessTokenRequest = - (): authorizationServerApi.AccessTokenRequest => ({ - client_id: generateId(), - // TODO: change to env variable - client_assertion_type: - "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", - client_assertion: getMockClientAssertion({ - customHeader: {}, - payload: {}, - customClaims: {}, - }), - grant_type: "client_credentials", - }); + (): authorizationServerApi.AccessTokenRequest => { + const keySet = crypto.generateKeyPairSync("rsa", { + modulusLength: 2048, + }); + + return { + client_id: generateId(), + // TODO: change to env variable + client_assertion_type: + "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", + client_assertion: getMockClientAssertion({ + customHeader: {}, + payload: {}, + customClaims: {}, + keySet, + }), + grant_type: "client_credentials", + }; + }; From 0105452df5c65fd7e3eb85cc8c12fbdffc59641f Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Thu, 12 Sep 2024 11:57:22 +0200 Subject: [PATCH 169/241] Improvements --- .../client-assertion-validation/src/errors.ts | 19 +- .../client-assertion-validation/src/types.ts | 4 +- .../client-assertion-validation/src/utils.ts | 209 ++++++++++-------- .../src/validation.ts | 35 +-- .../client-assertion-validation/test/utils.ts | 11 +- .../{sample.test.ts => validation.test.ts} | 204 ++++++++--------- 6 files changed, 250 insertions(+), 232 deletions(-) rename packages/client-assertion-validation/test/{sample.test.ts => validation.test.ts} (82%) diff --git a/packages/client-assertion-validation/src/errors.ts b/packages/client-assertion-validation/src/errors.ts index dbadc8551d..caaeefa7f0 100644 --- a/packages/client-assertion-validation/src/errors.ts +++ b/packages/client-assertion-validation/src/errors.ts @@ -1,4 +1,4 @@ -import { ApiError } from "pagopa-interop-models"; +import { ApiError, ClientKind } from "pagopa-interop-models"; export const errorCodes = { clientAssertionValidationFailure: "0001", @@ -35,12 +35,11 @@ export const errorCodes = { algorithmNotAllowed: "0032", purposeIdNotProvided: "0033", invalidKidFormat: "0034", + unexpectedKeyType: "0035", }; export type ErrorCodes = keyof typeof errorCodes; -// TODO: make api problem? - // TODO: missing errors: // - InvalidClientIdFormat -> check on uuid // - ClientAssertionParseFailed -> already handled in invalidClientAssertionFormat X @@ -48,8 +47,8 @@ export type ErrorCodes = keyof typeof errorCodes; // - InvalidSubjectFormat -> check on uuid of subject claim // - InvalidPurposeIdFormat -> check on uuid of purposeId claim (already covered by invalidPurposeIdClaimFormat?) // - DigestClaimNotFound -> check if custom claim digest exists -// - InvalidDigestClaims -> check if digest has only {alg, value} keys X we aren't discriminating from safeParse output -// - InvalidDigestFormat -> probably overlapping with previous. Check if digest is a JSON or a map ? // TODO: check if map works with safeParse +// - InvalidDigestClaims -> check if digest has only {alg, value} keys X we aren't discriminating between different safeParse errors +// - InvalidDigestFormat -> check object shape // - InvalidHashLength -> check on the length of digest.value (digest is a custom claim) // - InvalidHashAlgorithm -> check if digest.alg is sha256 // - AlgorithmNotFound -> check if header.alg is present @@ -341,3 +340,13 @@ export function invalidKidFormat(): ApiError { title: "Invalid KID format", }); } + +export function unexpectedKeyType( + clientKind: ClientKind +): ApiError { + return new ApiError({ + detail: `Key doesn't correspond to client kind: ${clientKind}`, + code: "unexpectedKeyType", + title: "Unexpected key type", + }); +} diff --git a/packages/client-assertion-validation/src/types.ts b/packages/client-assertion-validation/src/types.ts index 2672552bf8..8e68ffddee 100644 --- a/packages/client-assertion-validation/src/types.ts +++ b/packages/client-assertion-validation/src/types.ts @@ -44,7 +44,7 @@ export type ClientAssertion = z.infer; export const Key = z.object({ GSIPK_clientId: ClientId, consumerId: TenantId, - kidWithPurposeId: z.string(), // TO DO which field of the table is mapped to this? + kidWithPurposeId: z.string(), // TODO which field of the table is mapped to this? publicKey: z.string().min(1), algorithm: z.literal("RS256"), // no field to map from the table. Is it extracted from publicKey field? }); @@ -52,7 +52,7 @@ export type Key = z.infer; export const ConsumerKey = Key.extend({ clientKind: z.literal(clientKind.consumer), - GSIPK_purposeId: PurposeId, // to do is this naming ok? + GSIPK_purposeId: PurposeId, // TODO is this naming ok? purposeState: authorizationManagementApi.ClientComponentState, agreementId: AgreementId, agreementState: authorizationManagementApi.ClientComponentState, diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index 6510443ce6..bece86ac5b 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -9,10 +9,13 @@ import { import { ApiError, ClientId, + clientKind, PurposeId, unsafeBrandId, } from "pagopa-interop-models"; +import { match } from "ts-pattern"; import { + ApiKey, ClientAssertion, ClientAssertionDigest, ConsumerKey, @@ -53,11 +56,12 @@ import { invalidHashLength, invalidHashAlgorithm, invalidKidFormat, + unexpectedKeyType, } from "./errors.js"; -const CLIENT_ASSERTION_AUDIENCE = "test.interop.pagopa.it"; // To do: env? +const CLIENT_ASSERTION_AUDIENCE = "test.interop.pagopa.it"; // TODO: env? const EXPECTED_CLIENT_ASSERTION_TYPE = - "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; // To do: env? -const EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE = "client_credentials"; // To do: env? + "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; // TODO: env? +const EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE = "client_credentials"; // TODO: env? const ALLOWED_ALGORITHM = "RS256"; const ALLOWED_DIGEST_ALGORITHM = "SHA256"; @@ -293,92 +297,94 @@ export const verifyClientAssertion = ( clientAssertionJws: string, clientId: string | undefined ): ValidationResult => { - const decoded = decode(clientAssertionJws, { complete: true, json: true }); - - if (!decoded) { - return { errors: [invalidClientAssertionFormat()], data: undefined }; - } - - if (typeof decoded.payload === "string") { - return { errors: [unexpectedClientAssertionPayload()], data: undefined }; - } + try { + const decoded = decode(clientAssertionJws, { complete: true, json: true }); + if (!decoded) { + return { errors: [invalidClientAssertionFormat()], data: undefined }; + } - const { errors: jtiErrors, data: validatedJti } = validateJti( - decoded.payload.jti - ); - const { errors: iatErrors, data: validatedIat } = validateIat( - decoded.payload.iat - ); - const { errors: expErrors, data: validatedExp } = validateExp( - decoded.payload.exp - ); - const { errors: issErrors, data: validatedIss } = validateIss( - decoded.payload.iss - ); - const { errors: subErrors, data: validatedSub } = validateSub( - decoded.payload.sub, - clientId - ); - const { errors: purposeIdErrors, data: validatedPurposeId } = - validatePurposeId(decoded.payload.purposeId); - const { errors: kidErrors, data: validatedKid } = validateKid( - decoded.header.kid - ); - const { errors: audErrors, data: validatedAud } = validateAudience( - decoded.payload.aud - ); - const { errors: algErrors, data: validatedAlg } = validateAlgorithm( - decoded.header.alg - ); - const { errors: digestErrors, data: validatedDigest } = validateDigest( - decoded.payload.digest - ); + if (typeof decoded.payload === "string") { + return { errors: [unexpectedClientAssertionPayload()], data: undefined }; + } - if ( - !jtiErrors && - !iatErrors && - !expErrors && - !issErrors && - !subErrors && - !purposeIdErrors && - !kidErrors && - !audErrors && - !algErrors && - !digestErrors - ) { - const result: ClientAssertion = { - header: { - kid: validatedKid, - alg: validatedAlg, - }, - payload: { - sub: validatedSub, - purposeId: validatedPurposeId, - jti: validatedJti, - iat: validatedIat, - iss: validatedIss, - aud: validatedAud, - exp: validatedExp, // TODO Check unit of measure - digest: validatedDigest, - }, + const { errors: jtiErrors, data: validatedJti } = validateJti( + decoded.payload.jti + ); + const { errors: iatErrors, data: validatedIat } = validateIat( + decoded.payload.iat + ); + const { errors: expErrors, data: validatedExp } = validateExp( + decoded.payload.exp + ); + const { errors: issErrors, data: validatedIss } = validateIss( + decoded.payload.iss + ); + const { errors: subErrors, data: validatedSub } = validateSub( + decoded.payload.sub, + clientId + ); + const { errors: purposeIdErrors, data: validatedPurposeId } = + validatePurposeId(decoded.payload.purposeId); + const { errors: kidErrors, data: validatedKid } = validateKid( + decoded.header.kid + ); + const { errors: audErrors, data: validatedAud } = validateAudience( + decoded.payload.aud + ); + const { errors: algErrors, data: validatedAlg } = validateAlgorithm( + decoded.header.alg + ); + const { errors: digestErrors, data: validatedDigest } = validateDigest( + decoded.payload.digest + ); + if ( + !jtiErrors && + !iatErrors && + !expErrors && + !issErrors && + !subErrors && + !purposeIdErrors && + !kidErrors && + !audErrors && + !algErrors && + !digestErrors + ) { + const result: ClientAssertion = { + header: { + kid: validatedKid, + alg: validatedAlg, + }, + payload: { + sub: validatedSub, + purposeId: validatedPurposeId, + jti: validatedJti, + iat: validatedIat, + iss: validatedIss, + aud: validatedAud, + exp: validatedExp, + digest: validatedDigest, + }, + }; + return { errors: undefined, data: result }; + } + return { + errors: [ + ...(jtiErrors || []), + ...(iatErrors || []), + ...(expErrors || []), + ...(issErrors || []), + ...(subErrors || []), + ...(purposeIdErrors || []), + ...(kidErrors || []), + ...(audErrors || []), + ...(algErrors || []), + ...(digestErrors || []), + ], + data: undefined, }; - return { errors: undefined, data: result }; + } catch (error) { + return { errors: [unexpectedClientAssertionPayload()], data: undefined }; } - return { - errors: [ - ...(jtiErrors || []), - ...(iatErrors || []), - ...(expErrors || []), - ...(issErrors || []), - ...(subErrors || []), - ...(purposeIdErrors || []), - ...(kidErrors || []), - ...(audErrors || []), - ...(algErrors || []), - ...(digestErrors || []), - ], - data: undefined, - }; }; // export const b64Decode = (str: string): string => @@ -388,13 +394,12 @@ export const verifyClientAssertionSignature = ( clientAssertionJws: string, key: Key ): Array> | undefined => { - // todo: should this return a JwtPayload? Probably not try { const result = verify(clientAssertionJws, key.publicKey, { algorithms: [key.algorithm], }); - // TODO Improve this + // TODO: no idea when result is a string if (typeof result === "string") { return [invalidClientAssertionSignatureType(typeof result)]; } else { @@ -414,10 +419,9 @@ export const verifyClientAssertionSignature = ( } }; -export const assertValidPlatformState = ( +export const validatePlatformState = ( key: ConsumerKey ): Array> => { - // To do: is it ok to have these check throwing errors? So that they can be read if needed (instead of just getting false) const agreementError = key.agreementState !== "ACTIVE" ? inactiveAgreement() : undefined; @@ -431,3 +435,30 @@ export const assertValidPlatformState = ( (e) => e !== undefined ); }; + +export const validateClientKindAndPlatformState = ( + key: ApiKey, + jwt: ClientAssertion +): ValidationResult => + match(key.clientKind) + .with(clientKind.api, () => { + console.log("key", key); + console.log("safeParse", ApiKey.safeParse(key).success); + return !ApiKey.safeParse(key).success + ? { errors: [unexpectedKeyType(clientKind.api)], data: undefined } + : { data: jwt, errors: undefined }; + }) + .with(clientKind.consumer, () => { + if (ConsumerKey.safeParse(key).success) { + const platformStateErrors = validatePlatformState(key as ConsumerKey); + if (platformStateErrors.length === 0) { + return { data: jwt, errors: undefined }; + } + return { errors: platformStateErrors, data: undefined }; + } + return { + errors: [unexpectedKeyType(clientKind.consumer)], + data: undefined, + }; + }) + .exhaustive(); diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index f81b8ac20d..27d394df18 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -1,17 +1,15 @@ import { authorizationServerApi } from "pagopa-interop-api-clients"; -import { match } from "ts-pattern"; -import { clientKind } from "pagopa-interop-models"; import { verifyClientAssertionSignature, validateRequestParameters, - assertValidPlatformState, verifyClientAssertion, + validateClientKindAndPlatformState, } from "./utils.js"; import { ApiKey, ConsumerKey, ValidationResult } from "./types.js"; export const assertValidClientAssertion = async ( request: authorizationServerApi.AccessTokenRequest, - key: ConsumerKey | ApiKey // Todo use just Key? + key: ConsumerKey | ApiKey // TODO use just Key? ): Promise => { const parametersErrors = validateRequestParameters(request); @@ -23,7 +21,6 @@ export const assertValidClientAssertion = async ( key ); - // todo exit here if there are any errors so far? if ( parametersErrors || clientAssertionVerificationErrors || @@ -39,31 +36,5 @@ export const assertValidClientAssertion = async ( }; } - if (ApiKey.safeParse(key).success) { - return { data: jwt, errors: undefined }; - } - - // todo complete the part below - return match(key.clientKind) - .with(clientKind.api, () => { - const parsingErrors = !ApiKey.safeParse(key).success - ? [Error("parsing")] - : []; - - return [...parsingErrors]; - }) - .with(clientKind.consumer, () => { - const errors: Error[] = []; - if (ConsumerKey.safeParse(key).success) { - // to do: useful? - - // eslint-disable-next-line functional/immutable-data - errors.push(...assertValidPlatformState(key as ConsumerKey)); - } else { - // eslint-disable-next-line functional/immutable-data - errors.push(Error("parsing")); - } - return [...errors]; - }) - .exhaustive(); + return validateClientKindAndPlatformState(key, jwt); }; diff --git a/packages/client-assertion-validation/test/utils.ts b/packages/client-assertion-validation/test/utils.ts index b84209bf01..1a241b5d5e 100644 --- a/packages/client-assertion-validation/test/utils.ts +++ b/packages/client-assertion-validation/test/utils.ts @@ -10,6 +10,8 @@ import { } from "pagopa-interop-models"; import { ClientAssertionHeader, ConsumerKey } from ".././src/types"; +export const value64chars = crypto.randomBytes(32).toString("hex"); + export const getMockClientAssertion = ({ customHeader, payload, @@ -29,7 +31,10 @@ export const getMockClientAssertion = ({ exp: 60, jti: generateId(), iat: 5, - // ...customClaims, // TO DO: how many custom claims? Examples? + digest: { + alg: "SHA256", + value: value64chars, + }, }; const actualPayload = { @@ -39,7 +44,7 @@ export const getMockClientAssertion = ({ }; const options: jwt.SignOptions = { header: { - kid: "todo", + kid: "TODO", alg: "RS256", ...customHeader, }, @@ -59,7 +64,7 @@ export const getMockConsumerKey = (): ConsumerKey => ({ GSIPK_clientId: generateId(), consumerId: generateId(), kidWithPurposeId: "", - publicKey: "todo", + publicKey: "TODO", algorithm: "RS256", clientKind: "Consumer", GSIPK_purposeId: generateId(), diff --git a/packages/client-assertion-validation/test/sample.test.ts b/packages/client-assertion-validation/test/validation.test.ts similarity index 82% rename from packages/client-assertion-validation/test/sample.test.ts rename to packages/client-assertion-validation/test/validation.test.ts index 08c1224ae0..b4dd57e8fd 100644 --- a/packages/client-assertion-validation/test/sample.test.ts +++ b/packages/client-assertion-validation/test/validation.test.ts @@ -1,15 +1,18 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import crypto from "crypto"; +import { fail } from "assert"; import { describe, expect, it } from "vitest"; import { ApiError, ClientId, + clientKind, generateId, itemState, } from "pagopa-interop-models"; import * as jwt from "jsonwebtoken"; import { - assertValidPlatformState, + validateClientKindAndPlatformState, + validatePlatformState, validateRequestParameters, verifyClientAssertion, verifyClientAssertionSignature, @@ -44,16 +47,22 @@ import { unexpectedClientAssertionPayload, invalidDigestFormat, purposeIdNotProvided, + unexpectedKeyType, } from "../src/errors.js"; import { ConsumerKey } from "../src/types.js"; import { getMockAccessTokenRequest, getMockClientAssertion, getMockConsumerKey, + value64chars, } from "./utils.js"; -describe("test", () => { +describe("validation test", () => { describe("validateRequestParameters", () => { + it("success request parameters", () => { + expect(1).toBe(1); + }); + it("invalidAssertionType", () => { const wrongAssertionType = "something-wrong"; const request = { @@ -66,7 +75,6 @@ describe("test", () => { expect(errors![0]).toEqual(invalidAssertionType(wrongAssertionType)); }); // it("invalidGrantType", () => { - // // todo // const wrongGrantType = "something-wrong"; // const request = { // ...getMockAccessTokenRequest(), @@ -80,19 +88,16 @@ describe("test", () => { // }); }); - const value64chars = crypto.randomBytes(64).toString("ascii"); - describe("verifyClientAssertion", () => { + it("success client assertion", () => { + expect(1).toBe(1); + }); + it("invalidAudienceFormat", () => { const a = getMockClientAssertion({ customHeader: {}, payload: { aud: "random" }, - customClaims: { - digest: { - alg: "SHA256", - value: value64chars, - }, - }, + customClaims: {}, }); const { errors } = verifyClientAssertion(a, undefined); expect(errors).toBeDefined(); @@ -104,12 +109,7 @@ describe("test", () => { const a = getMockClientAssertion({ customHeader: {}, payload: { aud: ["random"] }, - customClaims: { - digest: { - alg: "SHA256", - value: value64chars, - }, - }, + customClaims: {}, }); const { errors } = verifyClientAssertion(a, undefined); expect(errors).toBeDefined(); @@ -141,9 +141,7 @@ describe("test", () => { expect(errors![0]).toEqual(invalidClientAssertionFormat()); }); - it.skip("unexpectedClientAssertionPayload", () => { - // to do: how to test? In this case the payload should be a string - + it("unexpectedClientAssertionPayload", () => { const key = crypto.generateKeyPairSync("rsa", { modulusLength: 2048, }).privateKey; @@ -166,12 +164,7 @@ describe("test", () => { const a = getMockClientAssertion({ customHeader: {}, payload: { jti: undefined }, - customClaims: { - digest: { - alg: "SHA256", - value: value64chars, - }, - }, + customClaims: {}, }); const { errors } = verifyClientAssertion(a, undefined); expect(errors).toBeDefined(); @@ -180,7 +173,7 @@ describe("test", () => { }); it.skip("iatNotFound", () => { - // to do: how to test? The sign function automatically adds iat if not present + // TODO: how to test? The sign function automatically adds iat if not present const a = getMockClientAssertion({ customHeader: {}, @@ -212,7 +205,7 @@ describe("test", () => { const options: jwt.SignOptions = { header: { - kid: "todo", + kid: "TODO", alg: "RS256", }, }; @@ -227,12 +220,7 @@ describe("test", () => { const jws = getMockClientAssertion({ customHeader: {}, payload: { iss: undefined }, - customClaims: { - digest: { - alg: "SHA256", - value: value64chars, - }, - }, + customClaims: {}, }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); @@ -244,12 +232,7 @@ describe("test", () => { const jws = getMockClientAssertion({ customHeader: {}, payload: { sub: undefined }, - customClaims: { - digest: { - alg: "SHA256", - value: value64chars, - }, - }, + customClaims: {}, }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); @@ -262,12 +245,7 @@ describe("test", () => { const jws = getMockClientAssertion({ customHeader: {}, payload: { sub: subject }, - customClaims: { - digest: { - alg: "SHA256", - value: value64chars, - }, - }, + customClaims: {}, }); const { errors } = verifyClientAssertion(jws, generateId()); expect(errors).toBeDefined(); @@ -281,12 +259,7 @@ describe("test", () => { const jws = getMockClientAssertion({ customHeader: {}, payload: { sub: subject }, - customClaims: { - digest: { - alg: "SHA256", - value: value64chars, - }, - }, + customClaims: {}, }); const { errors } = verifyClientAssertion(jws, clientId); expect(errors).toBeDefined(); @@ -301,10 +274,6 @@ describe("test", () => { payload: {}, customClaims: { purposeId: notPurposeId, - digest: { - alg: "SHA256", - value: value64chars, - }, }, }); const { errors } = verifyClientAssertion(jws, undefined); @@ -318,12 +287,7 @@ describe("test", () => { const jws = getMockClientAssertion({ customHeader: {}, payload: {}, - customClaims: { - digest: { - alg: "SHA256", - value: value64chars, - }, - }, + customClaims: {}, }); const { errors } = verifyClientAssertion(jws, notClientId); expect(errors).toBeDefined(); @@ -335,7 +299,9 @@ describe("test", () => { const jws = getMockClientAssertion({ customHeader: {}, payload: {}, - customClaims: {}, + customClaims: { + digest: undefined, + }, }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); @@ -343,7 +309,7 @@ describe("test", () => { expect(errors![0]).toEqual(digestClaimNotFound()); }); - it("invalidDigestClaims", () => { + it("invalidDigestFormat", () => { const jws = getMockClientAssertion({ customHeader: {}, payload: {}, @@ -360,7 +326,7 @@ describe("test", () => { customHeader: {}, payload: {}, customClaims: { - digest: { alg: "SHA256", value: "todo string of wrong length" }, + digest: { alg: "SHA256", value: "TODO string of wrong length" }, }, }); const { errors } = verifyClientAssertion(jws, undefined); @@ -384,13 +350,11 @@ describe("test", () => { }); it.skip("AlgorithmNotFound", () => { - // todo it seems this can't be tested because we need alg header to sign the mock jwt + // TODO it seems this can't be tested because we need alg header to sign the mock jwt const jws = getMockClientAssertion({ - customHeader: { alg: "undefined" }, + customHeader: { alg: undefined }, payload: {}, - customClaims: { - digest: { alg: "RS256", value: value64chars }, - }, + customClaims: {}, }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); @@ -403,9 +367,7 @@ describe("test", () => { const jws = getMockClientAssertion({ customHeader: { alg: "RS512" }, payload: {}, - customClaims: { - digest: { alg: "SHA256", value: value64chars }, - }, + customClaims: {}, }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); @@ -414,13 +376,11 @@ describe("test", () => { }); it.skip("purposeIdNotProvided", () => { - // todo this should be related to the case of consumerKey + // TODO this should be related to the case of consumerKey const jws = getMockClientAssertion({ customHeader: {}, payload: { purposeId: undefined }, - customClaims: { - digest: { alg: "SHA256", value: value64chars }, - }, + customClaims: {}, }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); @@ -432,9 +392,7 @@ describe("test", () => { const jws = getMockClientAssertion({ customHeader: { kid: "not-a-valid-kid" }, payload: {}, - customClaims: { - digest: { alg: "SHA256", value: value64chars }, - }, + customClaims: {}, }); const { errors } = verifyClientAssertion(jws, undefined); expect(errors).toBeDefined(); @@ -444,12 +402,15 @@ describe("test", () => { }); describe("verifyClientAssertionSignature", () => { - it("invalidClientAssertionSignatureType", () => { - // todo: find out when the jwonwebtoken.verify functin returns a string + it("success client assertion signature", () => { + expect(1).toBe(1); + }); + + it.skip("invalidClientAssertionSignatureType", () => { + // TODO: find out when the jwonwebtoken.verify function returns a string expect(1).toBe(1); }); it("tokenExpiredError", () => { - // todo why does it fail? const sixHoursAgo = new Date(); sixHoursAgo.setHours(sixHoursAgo.getHours() - 6); @@ -466,12 +427,9 @@ describe("test", () => { iat: sixHoursAgo.getTime() / 1000, exp: threeHourAgo.getTime() / 1000, }, - customClaims: { - digest: { alg: "SHA256", value: value64chars }, - }, + customClaims: {}, keySet, }); - // TODO: how to get string from keySet.publicKey const publicKey = keySet.publicKey .export({ type: "pkcs1", @@ -495,8 +453,6 @@ describe("test", () => { expect(errors![0].title).toEqual(jsonWebTokenError("").title); }); it("notBeforeError", () => { - // todo why does it fail? - const threeHoursAgo = new Date(); threeHoursAgo.setHours(threeHoursAgo.getHours() - 3); @@ -516,9 +472,7 @@ describe("test", () => { exp: sixHoursLater.getTime() / 1000, nbf: threeHoursLater.getTime() / 1000, }, - customClaims: { - digest: { alg: "SHA256", value: value64chars }, - }, + customClaims: {}, keySet, }); const publicKey = keySet.publicKey.export({ @@ -535,20 +489,24 @@ describe("test", () => { expect(errors).toHaveLength(1); expect(errors![0]).toEqual(notBeforeError()); }); - it("clientAssertionSignatureVerificationFailure", () => { - // todo + it.skip("clientAssertionSignatureVerificationFailure", () => { + // TODO: not sure when this happens expect(1).toBe(1); }); }); - describe("assertValidPlatformStates", () => { + describe("validatePlatformState", () => { + it("success", () => { + expect(1).toBe(1); + }); + it("inactiveAgreement", () => { const mockKey: ConsumerKey = { ...getMockConsumerKey(), agreementState: itemState.inactive, }; - assertValidPlatformState(mockKey); - const errors = assertValidPlatformState(mockKey); + validatePlatformState(mockKey); + const errors = validatePlatformState(mockKey); expect(errors).toBeDefined(); expect(errors).toHaveLength(1); @@ -559,8 +517,8 @@ describe("test", () => { ...getMockConsumerKey(), descriptorState: itemState.inactive, }; - assertValidPlatformState(mockKey); - const errors = assertValidPlatformState(mockKey); + validatePlatformState(mockKey); + const errors = validatePlatformState(mockKey); expect(errors).toBeDefined(); expect(errors).toHaveLength(1); @@ -571,14 +529,58 @@ describe("test", () => { ...getMockConsumerKey(), purposeState: itemState.inactive, }; - assertValidPlatformState(mockKey); - const errors = assertValidPlatformState(mockKey); + validatePlatformState(mockKey); + const errors = validatePlatformState(mockKey); expect(errors).toBeDefined(); expect(errors).toHaveLength(1); expect(errors[0]).toEqual(inactivePurpose()); }); }); + + describe("validateClientKindAndPlatformState", () => { + it.only("unexpectedKeyType (consumerKey and clientKind.api)", () => { + const mockConsumerKey = { + ...getMockConsumerKey(), + clientKind: clientKind.api, + }; + const { data: mockClientAssertion } = verifyClientAssertion( + getMockClientAssertion({ + customHeader: {}, + payload: {}, + customClaims: {}, + }), + undefined + ); + if (!mockClientAssertion) { + fail(); + } + const { data, errors } = validateClientKindAndPlatformState( + mockConsumerKey, + mockClientAssertion + ); + console.log("data", data); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors![0]).toEqual(unexpectedKeyType(mockConsumerKey.clientKind)); + }); + + it.only("unexpectedKeyType (apiKey and clientKind.consumer)", () => { + expect(1).toBe(1); + }); + + it.only("success (consumerKey and clientKind.consumer; valid platform states)", () => { + expect(1).toBe(1); + }); + + it.only("inactiveEService (consumerKey and clientKind.consumer; invalid platform states)", () => { + expect(1).toBe(1); + }); + + it.only("unexpectedKeyType (apiKey and clientKind.api)", () => { + expect(1).toBe(1); + }); + }); }); const printErrors = (errors?: Array>): void => { From 98784fcaced56e44014d55ea6f59bf9dd2925bdd Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Thu, 12 Sep 2024 12:49:13 +0200 Subject: [PATCH 170/241] Improvements --- .../client-assertion-validation/src/errors.ts | 4 +- .../client-assertion-validation/src/types.ts | 78 ++++++++------ .../client-assertion-validation/src/utils.ts | 23 ++-- .../src/validation.ts | 2 +- .../client-assertion-validation/test/utils.ts | 14 ++- .../test/validation.test.ts | 101 +++++++++++++++--- 6 files changed, 159 insertions(+), 63 deletions(-) diff --git a/packages/client-assertion-validation/src/errors.ts b/packages/client-assertion-validation/src/errors.ts index caaeefa7f0..ed64b75eb6 100644 --- a/packages/client-assertion-validation/src/errors.ts +++ b/packages/client-assertion-validation/src/errors.ts @@ -1,4 +1,4 @@ -import { ApiError, ClientKind } from "pagopa-interop-models"; +import { ApiError, ClientKindTokenStates } from "pagopa-interop-models"; export const errorCodes = { clientAssertionValidationFailure: "0001", @@ -342,7 +342,7 @@ export function invalidKidFormat(): ApiError { } export function unexpectedKeyType( - clientKind: ClientKind + clientKind: ClientKindTokenStates ): ApiError { return new ApiError({ detail: `Key doesn't correspond to client kind: ${clientKind}`, diff --git a/packages/client-assertion-validation/src/types.ts b/packages/client-assertion-validation/src/types.ts index 8e68ffddee..0c11736d1b 100644 --- a/packages/client-assertion-validation/src/types.ts +++ b/packages/client-assertion-validation/src/types.ts @@ -3,7 +3,7 @@ import { AgreementId, ApiError, ClientId, - clientKind, + clientKindTokenStates, EServiceId, PurposeId, TenantId, @@ -11,59 +11,69 @@ import { import { z } from "zod"; import { ErrorCodes } from "./errors.js"; -export const ClientAssertionDigest = z.object({ - alg: z.string(), - value: z.string(), -}); +export const ClientAssertionDigest = z + .object({ + alg: z.string(), + value: z.string(), + }) + .strict(); export type ClientAssertionDigest = z.infer; -export const ClientAssertionHeader = z.object({ - kid: z.string(), - alg: z.string(), // TODO Enum, which values? -}); +export const ClientAssertionHeader = z + .object({ + kid: z.string(), + alg: z.string(), // TODO Enum, which values? + }) + .strict(); export type ClientAssertionHeader = z.infer; -export const ClientAssertionPayload = z.object({ - sub: z.string(), - jti: z.string(), - iat: z.number(), - iss: z.string(), - aud: z.array(z.string()), - exp: z.number(), - digest: ClientAssertionDigest, - purposeId: PurposeId.optional(), -}); +export const ClientAssertionPayload = z + .object({ + sub: z.string(), + jti: z.string(), + iat: z.number(), + iss: z.string(), + aud: z.array(z.string()), + exp: z.number(), + digest: ClientAssertionDigest, + purposeId: PurposeId.optional(), + }) + .strict(); export type ClientAssertionPayload = z.infer; -export const ClientAssertion = z.object({ - header: ClientAssertionHeader, - payload: ClientAssertionPayload, -}); +export const ClientAssertion = z + .object({ + header: ClientAssertionHeader, + payload: ClientAssertionPayload, + }) + .strict(); export type ClientAssertion = z.infer; -export const Key = z.object({ - GSIPK_clientId: ClientId, - consumerId: TenantId, - kidWithPurposeId: z.string(), // TODO which field of the table is mapped to this? - publicKey: z.string().min(1), - algorithm: z.literal("RS256"), // no field to map from the table. Is it extracted from publicKey field? -}); +export const Key = z + .object({ + GSIPK_clientId: ClientId, + consumerId: TenantId, + kidWithPurposeId: z.string(), // TODO which field of the table is mapped to this? + publicKey: z.string().min(1), + algorithm: z.literal("RS256"), // no field to map from the table. Is it extracted from publicKey field? + }) + .strict(); export type Key = z.infer; export const ConsumerKey = Key.extend({ - clientKind: z.literal(clientKind.consumer), + clientKind: z.literal(clientKindTokenStates.consumer), GSIPK_purposeId: PurposeId, // TODO is this naming ok? purposeState: authorizationManagementApi.ClientComponentState, agreementId: AgreementId, agreementState: authorizationManagementApi.ClientComponentState, eServiceId: EServiceId, // no field to map. Extract from GSIPK_eserviceId_descriptorId? descriptorState: authorizationManagementApi.ClientComponentState, -}); +}).strict(); export type ConsumerKey = z.infer; export const ApiKey = Key.extend({ - clientKind: z.literal(clientKind.api), -}); + clientKind: z.literal(clientKindTokenStates.api), +}).strict(); export type ApiKey = z.infer; export type ValidationResult = diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index bece86ac5b..872f1a6684 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -9,7 +9,7 @@ import { import { ApiError, ClientId, - clientKind, + clientKindTokenStates, PurposeId, unsafeBrandId, } from "pagopa-interop-models"; @@ -437,18 +437,19 @@ export const validatePlatformState = ( }; export const validateClientKindAndPlatformState = ( - key: ApiKey, + key: ApiKey | ConsumerKey, jwt: ClientAssertion ): ValidationResult => match(key.clientKind) - .with(clientKind.api, () => { - console.log("key", key); - console.log("safeParse", ApiKey.safeParse(key).success); - return !ApiKey.safeParse(key).success - ? { errors: [unexpectedKeyType(clientKind.api)], data: undefined } - : { data: jwt, errors: undefined }; - }) - .with(clientKind.consumer, () => { + .with(clientKindTokenStates.api, () => + !ApiKey.safeParse(key).success + ? { + errors: [unexpectedKeyType(clientKindTokenStates.api)], + data: undefined, + } + : { data: jwt, errors: undefined } + ) + .with(clientKindTokenStates.consumer, () => { if (ConsumerKey.safeParse(key).success) { const platformStateErrors = validatePlatformState(key as ConsumerKey); if (platformStateErrors.length === 0) { @@ -457,7 +458,7 @@ export const validateClientKindAndPlatformState = ( return { errors: platformStateErrors, data: undefined }; } return { - errors: [unexpectedKeyType(clientKind.consumer)], + errors: [unexpectedKeyType(clientKindTokenStates.consumer)], data: undefined, }; }) diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index 27d394df18..066e409662 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -7,7 +7,7 @@ import { } from "./utils.js"; import { ApiKey, ConsumerKey, ValidationResult } from "./types.js"; -export const assertValidClientAssertion = async ( +export const validateClientAssertion = async ( request: authorizationServerApi.AccessTokenRequest, key: ConsumerKey | ApiKey // TODO use just Key? ): Promise => { diff --git a/packages/client-assertion-validation/test/utils.ts b/packages/client-assertion-validation/test/utils.ts index 1a241b5d5e..da69b6a477 100644 --- a/packages/client-assertion-validation/test/utils.ts +++ b/packages/client-assertion-validation/test/utils.ts @@ -3,12 +3,13 @@ import { authorizationServerApi } from "pagopa-interop-api-clients"; import * as jwt from "jsonwebtoken"; import { ClientId, + clientKindTokenStates, generateId, itemState, PurposeId, TenantId, } from "pagopa-interop-models"; -import { ClientAssertionHeader, ConsumerKey } from ".././src/types"; +import { ApiKey, ClientAssertionHeader, ConsumerKey } from ".././src/types"; export const value64chars = crypto.randomBytes(32).toString("hex"); @@ -66,7 +67,7 @@ export const getMockConsumerKey = (): ConsumerKey => ({ kidWithPurposeId: "", publicKey: "TODO", algorithm: "RS256", - clientKind: "Consumer", + clientKind: clientKindTokenStates.consumer, GSIPK_purposeId: generateId(), purposeState: itemState.active, agreementId: generateId(), @@ -75,6 +76,15 @@ export const getMockConsumerKey = (): ConsumerKey => ({ descriptorState: itemState.active, }); +export const getMockApiKey = (): ApiKey => ({ + GSIPK_clientId: generateId(), + consumerId: generateId(), + kidWithPurposeId: "", + publicKey: "TODO", + algorithm: "RS256", + clientKind: clientKindTokenStates.api, +}); + export const getMockAccessTokenRequest = (): authorizationServerApi.AccessTokenRequest => { const keySet = crypto.generateKeyPairSync("rsa", { diff --git a/packages/client-assertion-validation/test/validation.test.ts b/packages/client-assertion-validation/test/validation.test.ts index b4dd57e8fd..d75791bf17 100644 --- a/packages/client-assertion-validation/test/validation.test.ts +++ b/packages/client-assertion-validation/test/validation.test.ts @@ -5,7 +5,7 @@ import { describe, expect, it } from "vitest"; import { ApiError, ClientId, - clientKind, + clientKindTokenStates, generateId, itemState, } from "pagopa-interop-models"; @@ -52,6 +52,7 @@ import { import { ConsumerKey } from "../src/types.js"; import { getMockAccessTokenRequest, + getMockApiKey, getMockClientAssertion, getMockConsumerKey, value64chars, @@ -539,10 +540,10 @@ describe("validation test", () => { }); describe("validateClientKindAndPlatformState", () => { - it.only("unexpectedKeyType (consumerKey and clientKind.api)", () => { + it("unexpectedKeyType (consumerKey and clientKind.api)", () => { const mockConsumerKey = { ...getMockConsumerKey(), - clientKind: clientKind.api, + clientKind: clientKindTokenStates.api, }; const { data: mockClientAssertion } = verifyClientAssertion( getMockClientAssertion({ @@ -555,30 +556,104 @@ describe("validation test", () => { if (!mockClientAssertion) { fail(); } - const { data, errors } = validateClientKindAndPlatformState( + const { errors } = validateClientKindAndPlatformState( mockConsumerKey, mockClientAssertion ); - console.log("data", data); expect(errors).toBeDefined(); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(unexpectedKeyType(mockConsumerKey.clientKind)); }); - it.only("unexpectedKeyType (apiKey and clientKind.consumer)", () => { - expect(1).toBe(1); + it("unexpectedKeyType (apiKey and clientKindTokenStates.consumer)", () => { + const mockApiKey = { + ...getMockApiKey(), + clientKind: clientKindTokenStates.consumer, + }; + const { data: mockClientAssertion } = verifyClientAssertion( + getMockClientAssertion({ + customHeader: {}, + payload: {}, + customClaims: {}, + }), + undefined + ); + if (!mockClientAssertion) { + fail(); + } + const { errors } = validateClientKindAndPlatformState( + // FIX + mockApiKey, + mockClientAssertion + ); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors![0]).toEqual(unexpectedKeyType(mockApiKey.clientKind)); }); - it.only("success (consumerKey and clientKind.consumer; valid platform states)", () => { - expect(1).toBe(1); + it("success (consumerKey and clientKindTokenStates.consumer; valid platform states)", () => { + const mockConsumerKey = getMockConsumerKey(); + const { data: mockClientAssertion } = verifyClientAssertion( + getMockClientAssertion({ + customHeader: {}, + payload: {}, + customClaims: {}, + }), + undefined + ); + if (!mockClientAssertion) { + fail(); + } + const { errors } = validateClientKindAndPlatformState( + mockConsumerKey, + mockClientAssertion + ); + expect(errors).toBeUndefined(); }); - it.only("inactiveEService (consumerKey and clientKind.consumer; invalid platform states)", () => { - expect(1).toBe(1); + it("inactiveEService (consumerKey and clientKindTokenStates.consumer; invalid platform states)", () => { + const mockConsumerKey: ConsumerKey = { + ...getMockConsumerKey(), + descriptorState: itemState.inactive, + }; + const { data: mockClientAssertion } = verifyClientAssertion( + getMockClientAssertion({ + customHeader: {}, + payload: {}, + customClaims: {}, + }), + undefined + ); + if (!mockClientAssertion) { + fail(); + } + const { errors } = validateClientKindAndPlatformState( + mockConsumerKey, + mockClientAssertion + ); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors![0]).toEqual(inactiveEService()); }); - it.only("unexpectedKeyType (apiKey and clientKind.api)", () => { - expect(1).toBe(1); + it("success (apiKey and clientKindTokenStates.api)", () => { + const mockApiKey = getMockApiKey(); + const { data: mockClientAssertion } = verifyClientAssertion( + getMockClientAssertion({ + customHeader: {}, + payload: {}, + customClaims: {}, + }), + undefined + ); + if (!mockClientAssertion) { + fail(); + } + const { errors } = validateClientKindAndPlatformState( + mockApiKey, + mockClientAssertion + ); + expect(errors).toBeUndefined(); }); }); }); From 04c7aa48a1bbb61d622f77ccda1953cda5a93aea Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 12 Sep 2024 14:38:43 +0200 Subject: [PATCH 171/241] Fix --- .../client-assertion-validation/src/utils.ts | 12 ++-- .../test/validation.test.ts | 62 +++++++++---------- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index 872f1a6684..29bd61bd3e 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -82,7 +82,9 @@ export const validateRequestParameters = ( if (!assertionTypeError && !grantTypeError) { return undefined; } - return [assertionTypeError, grantTypeError].filter((e) => e !== undefined); + return [assertionTypeError, grantTypeError].filter( + (e) => e !== undefined + ) as Array>; }; const validateJti = (jti?: string): FlexibleValidationResult => { @@ -162,7 +164,7 @@ const validateSub = ( return { errors: [clientIdError, invalidSubFormatError].filter( (e) => e !== undefined - ), + ) as Array>, data: undefined, }; } @@ -287,7 +289,9 @@ const validateDigest = ( }; } return { - errors: [digestLengthError, digestAlgError].filter((e) => e !== undefined), + errors: [digestLengthError, digestAlgError].filter( + (e) => e !== undefined + ) as Array>, data: undefined, }; }; @@ -433,7 +437,7 @@ export const validatePlatformState = ( return [agreementError, descriptorError, purposeError].filter( (e) => e !== undefined - ); + ) as Array>; }; export const validateClientKindAndPlatformState = ( diff --git a/packages/client-assertion-validation/test/validation.test.ts b/packages/client-assertion-validation/test/validation.test.ts index d75791bf17..5a1d356cd7 100644 --- a/packages/client-assertion-validation/test/validation.test.ts +++ b/packages/client-assertion-validation/test/validation.test.ts @@ -3,7 +3,6 @@ import crypto from "crypto"; import { fail } from "assert"; import { describe, expect, it } from "vitest"; import { - ApiError, ClientId, clientKindTokenStates, generateId, @@ -21,7 +20,6 @@ import { algorithmNotAllowed, algorithmNotFound, digestClaimNotFound, - ErrorCodes, expNotFound, inactiveAgreement, inactiveEService, @@ -565,31 +563,31 @@ describe("validation test", () => { expect(errors![0]).toEqual(unexpectedKeyType(mockConsumerKey.clientKind)); }); - it("unexpectedKeyType (apiKey and clientKindTokenStates.consumer)", () => { - const mockApiKey = { - ...getMockApiKey(), - clientKind: clientKindTokenStates.consumer, - }; - const { data: mockClientAssertion } = verifyClientAssertion( - getMockClientAssertion({ - customHeader: {}, - payload: {}, - customClaims: {}, - }), - undefined - ); - if (!mockClientAssertion) { - fail(); - } - const { errors } = validateClientKindAndPlatformState( - // FIX - mockApiKey, - mockClientAssertion - ); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(unexpectedKeyType(mockApiKey.clientKind)); - }); + // it("unexpectedKeyType (apiKey and clientKindTokenStates.consumer)", () => { + // const mockApiKey = { + // ...getMockApiKey(), + // clientKind: clientKindTokenStates.consumer, + // }; + // const { data: mockClientAssertion } = verifyClientAssertion( + // getMockClientAssertion({ + // customHeader: {}, + // payload: {}, + // customClaims: {}, + // }), + // undefined + // ); + // if (!mockClientAssertion) { + // fail(); + // } + // const { errors } = validateClientKindAndPlatformState( + // // FIX + // mockApiKey, + // mockClientAssertion + // ); + // expect(errors).toBeDefined(); + // expect(errors).toHaveLength(1); + // expect(errors![0]).toEqual(unexpectedKeyType(mockApiKey.clientKind)); + // }); it("success (consumerKey and clientKindTokenStates.consumer; valid platform states)", () => { const mockConsumerKey = getMockConsumerKey(); @@ -658,8 +656,8 @@ describe("validation test", () => { }); }); -const printErrors = (errors?: Array>): void => { - if (errors) { - errors.forEach((e) => console.log(e.code, e.detail)); - } -}; +// const printErrors = (errors?: Array>): void => { +// if (errors) { +// errors.forEach((e) => console.log(e.code, e.detail)); +// } +// }; From 762d65b78c8e7edece5a91e048444a9e76191533 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 12 Sep 2024 14:47:07 +0200 Subject: [PATCH 172/241] Fix import --- packages/client-assertion-validation/test/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client-assertion-validation/test/utils.ts b/packages/client-assertion-validation/test/utils.ts index da69b6a477..78985dae91 100644 --- a/packages/client-assertion-validation/test/utils.ts +++ b/packages/client-assertion-validation/test/utils.ts @@ -9,7 +9,7 @@ import { PurposeId, TenantId, } from "pagopa-interop-models"; -import { ApiKey, ClientAssertionHeader, ConsumerKey } from ".././src/types"; +import { ApiKey, ClientAssertionHeader, ConsumerKey } from ".././src/types.js"; export const value64chars = crypto.randomBytes(32).toString("hex"); From 8ea221247d39b32e5ebfb84c978ecaa29200b63b Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 12 Sep 2024 14:47:15 +0200 Subject: [PATCH 173/241] Refactor --- packages/client-assertion-validation/src/utils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index 29bd61bd3e..23e04037b2 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -446,12 +446,12 @@ export const validateClientKindAndPlatformState = ( ): ValidationResult => match(key.clientKind) .with(clientKindTokenStates.api, () => - !ApiKey.safeParse(key).success - ? { + ApiKey.safeParse(key).success + ? { data: jwt, errors: undefined } + : { errors: [unexpectedKeyType(clientKindTokenStates.api)], data: undefined, } - : { data: jwt, errors: undefined } ) .with(clientKindTokenStates.consumer, () => { if (ConsumerKey.safeParse(key).success) { From 2fa65e009d9674403d2c9e65c1a1732f3293804d Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 12 Sep 2024 14:57:48 +0200 Subject: [PATCH 174/241] Add check --- packages/client-assertion-validation/src/utils.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index 23e04037b2..ea7b57fb17 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -57,6 +57,7 @@ import { invalidHashAlgorithm, invalidKidFormat, unexpectedKeyType, + purposeIdNotProvided, } from "./errors.js"; const CLIENT_ASSERTION_AUDIENCE = "test.interop.pagopa.it"; // TODO: env? const EXPECTED_CLIENT_ASSERTION_TYPE = @@ -456,10 +457,15 @@ export const validateClientKindAndPlatformState = ( .with(clientKindTokenStates.consumer, () => { if (ConsumerKey.safeParse(key).success) { const platformStateErrors = validatePlatformState(key as ConsumerKey); - if (platformStateErrors.length === 0) { - return { data: jwt, errors: undefined }; - } - return { errors: platformStateErrors, data: undefined }; + const purposeIdError = jwt.payload.purposeId + ? undefined + : purposeIdNotProvided(); + return { + errors: [...platformStateErrors, purposeIdError].filter( + (e) => e !== undefined + ) as Array>, + data: undefined, + }; } return { errors: [unexpectedKeyType(clientKindTokenStates.consumer)], From 1f8aa0ed629a2ed99225d70fce1f344debc93f16 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 12 Sep 2024 14:57:53 +0200 Subject: [PATCH 175/241] Fix test --- .../test/validation.test.ts | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/packages/client-assertion-validation/test/validation.test.ts b/packages/client-assertion-validation/test/validation.test.ts index 5a1d356cd7..0a3d009a70 100644 --- a/packages/client-assertion-validation/test/validation.test.ts +++ b/packages/client-assertion-validation/test/validation.test.ts @@ -7,6 +7,7 @@ import { clientKindTokenStates, generateId, itemState, + PurposeId, } from "pagopa-interop-models"; import * as jwt from "jsonwebtoken"; import { @@ -374,19 +375,6 @@ describe("validation test", () => { expect(errors![0]).toEqual(algorithmNotAllowed(notAllowedAlg)); }); - it.skip("purposeIdNotProvided", () => { - // TODO this should be related to the case of consumerKey - const jws = getMockClientAssertion({ - customHeader: {}, - payload: { purposeId: undefined }, - customClaims: {}, - }); - const { errors } = verifyClientAssertion(jws, undefined); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(purposeIdNotProvided()); - }); - it("InvalidKidFormat", () => { const jws = getMockClientAssertion({ customHeader: { kid: "not-a-valid-kid" }, @@ -594,7 +582,7 @@ describe("validation test", () => { const { data: mockClientAssertion } = verifyClientAssertion( getMockClientAssertion({ customHeader: {}, - payload: {}, + payload: { purposeId: generateId() }, customClaims: {}, }), undefined @@ -617,7 +605,7 @@ describe("validation test", () => { const { data: mockClientAssertion } = verifyClientAssertion( getMockClientAssertion({ customHeader: {}, - payload: {}, + payload: { purposeId: generateId() }, customClaims: {}, }), undefined @@ -653,6 +641,29 @@ describe("validation test", () => { ); expect(errors).toBeUndefined(); }); + + it("purposeIdNotProvided", () => { + // TODO this should be related to the case of consumerKey + const mockConsumerKey = getMockConsumerKey(); + const { data: mockClientAssertion } = verifyClientAssertion( + getMockClientAssertion({ + customHeader: {}, + payload: { purposeId: undefined }, + customClaims: {}, + }), + undefined + ); + if (!mockClientAssertion) { + fail(); + } + const { errors } = validateClientKindAndPlatformState( + mockConsumerKey, + mockClientAssertion + ); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors![0]).toEqual(purposeIdNotProvided()); + }); }); }); From 9a19717bc23a71956846dd5985e1b939c75e9dc2 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 12 Sep 2024 15:08:12 +0200 Subject: [PATCH 176/241] Fix --- packages/client-assertion-validation/src/utils.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index ea7b57fb17..661c9e79b2 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -460,6 +460,10 @@ export const validateClientKindAndPlatformState = ( const purposeIdError = jwt.payload.purposeId ? undefined : purposeIdNotProvided(); + + if (platformStateErrors.length === 0 && !purposeIdError) { + return { errors: undefined, data: jwt }; + } return { errors: [...platformStateErrors, purposeIdError].filter( (e) => e !== undefined From d212b0990eda855431a01370f04f4fb22a0cdf3c Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 12 Sep 2024 15:39:55 +0200 Subject: [PATCH 177/241] Refactor --- .../client-assertion-validation/src/types.ts | 20 +- .../client-assertion-validation/src/utils.ts | 237 ++++++------------ .../src/validation.ts | 9 +- 3 files changed, 98 insertions(+), 168 deletions(-) diff --git a/packages/client-assertion-validation/src/types.ts b/packages/client-assertion-validation/src/types.ts index 0c11736d1b..25f8e4683c 100644 --- a/packages/client-assertion-validation/src/types.ts +++ b/packages/client-assertion-validation/src/types.ts @@ -76,16 +76,16 @@ export const ApiKey = Key.extend({ }).strict(); export type ApiKey = z.infer; -export type ValidationResult = - | { errors: undefined; data: ClientAssertion } - | { errors: Array>; data: undefined }; +export type ValidationResult = SuccessfulValidation | FailedValidation; -export type FlexibleValidationResult = - | { errors: undefined; data: T } - | { errors: Array>; data: undefined }; - -// alternatively -export type ValidationResult2 = { +export type SuccessfulValidation = { errors: undefined; data: T }; +export type FailedValidation = { errors: Array>; - data?: ClientAssertion; + data: undefined; }; + +// // alternatively +// export type ValidationResult2 = { +// errors: Array>; +// data?: ClientAssertion; +// }; diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index 661c9e79b2..d9fbc4fec1 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -19,9 +19,10 @@ import { ClientAssertion, ClientAssertionDigest, ConsumerKey, - FlexibleValidationResult, - Key, + FailedValidation, ValidationResult, + Key, + SuccessfulValidation, } from "./types.js"; import { ErrorCodes, @@ -88,71 +89,44 @@ export const validateRequestParameters = ( ) as Array>; }; -const validateJti = (jti?: string): FlexibleValidationResult => { +const validateJti = (jti?: string): ValidationResult => { if (!jti) { - return { - errors: [jtiNotFound()], - data: undefined, - }; + return failedValidation([jtiNotFound()]); } else { - return { - errors: undefined, - data: jti, - }; + return successfulValidation(jti); } }; -const validateIat = (iat?: number): FlexibleValidationResult => { +const validateIat = (iat?: number): ValidationResult => { if (!iat) { - return { - errors: [issuedAtNotFound()], - data: undefined, - }; + return failedValidation([issuedAtNotFound()]); } else { - return { - errors: undefined, - data: iat, - }; + return successfulValidation(iat); } }; -const validateExp = (exp?: number): FlexibleValidationResult => { +const validateExp = (exp?: number): ValidationResult => { if (!exp) { - return { - errors: [expNotFound()], - data: undefined, - }; + return failedValidation([expNotFound()]); } else { - return { - errors: undefined, - data: exp, - }; + return successfulValidation(exp); } }; -const validateIss = (iss?: string): FlexibleValidationResult => { +const validateIss = (iss?: string): ValidationResult => { if (!iss) { - return { - errors: [issuerNotFound()], - data: undefined, - }; + return failedValidation([issuerNotFound()]); } else { - return { - errors: undefined, - data: iss, - }; + return successfulValidation(iss); } }; const validateSub = ( sub?: string, clientId?: string -): FlexibleValidationResult => { +): ValidationResult => { if (!sub) { - return { - errors: [subjectNotFound()], - data: undefined, - }; + return failedValidation([subjectNotFound()]); } if (clientId) { const clientIdError = !ClientId.safeParse(clientId).success @@ -162,117 +136,76 @@ const validateSub = ( ? invalidSubjectFormat(sub) : undefined; if (clientIdError || invalidSubFormatError) { - return { - errors: [clientIdError, invalidSubFormatError].filter( - (e) => e !== undefined - ) as Array>, - data: undefined, - }; + return failedValidation([clientIdError, invalidSubFormatError]); } // TODO: clientId undefined OK? if (sub !== clientId) { - return { - errors: [invalidSubject(sub)], - data: undefined, - }; + return failedValidation([invalidSubject(sub)]); } } - return { - errors: undefined, - data: sub, - }; + return successfulValidation(sub); }; const validatePurposeId = ( purposeId?: string -): FlexibleValidationResult => { +): ValidationResult => { if (purposeId && !PurposeId.safeParse(purposeId).success) { - return { - errors: [invalidPurposeIdClaimFormat(purposeId)], - data: undefined, - }; + return failedValidation([invalidPurposeIdClaimFormat(purposeId)]); } else { - return { - errors: undefined, - data: purposeId ? unsafeBrandId(purposeId) : undefined, - }; + const validatedPurposeId = purposeId + ? unsafeBrandId(purposeId) + : undefined; + return successfulValidation(validatedPurposeId); } }; -const validateKid = (kid?: string): FlexibleValidationResult => { +const validateKid = (kid?: string): ValidationResult => { if (!kid) { - return { - errors: [kidNotFound()], - data: undefined, - }; + return failedValidation([kidNotFound()]); } const alphanumericRegex = new RegExp("^[a-zA-Z0-9]+$"); if (alphanumericRegex.test(kid)) { - return { - errors: undefined, - data: kid, - }; + return successfulValidation(kid); } - return { - errors: [invalidKidFormat()], - data: undefined, - }; + return failedValidation([invalidKidFormat()]); }; const validateAudience = ( aud: string | string[] | undefined -): FlexibleValidationResult => { +): ValidationResult => { if (aud === CLIENT_ASSERTION_AUDIENCE) { - return { errors: undefined, data: [aud] }; + return successfulValidation([aud]); } if (!Array.isArray(aud)) { - return { - errors: [invalidAudienceFormat()], - data: undefined, - }; + return failedValidation([invalidAudienceFormat()]); } else { if (!aud.includes(CLIENT_ASSERTION_AUDIENCE)) { - return { errors: [invalidAudience()], data: undefined }; + return failedValidation([invalidAudience()]); } - return { errors: undefined, data: aud }; + return successfulValidation(aud); } }; -const validateAlgorithm = (alg?: string): FlexibleValidationResult => { +const validateAlgorithm = (alg?: string): ValidationResult => { if (!alg) { - return { - errors: [algorithmNotFound()], - data: undefined, - }; + return failedValidation([algorithmNotFound()]); } if (alg === ALLOWED_ALGORITHM) { - return { - errors: undefined, - data: alg, - }; + return successfulValidation(alg); } - return { - errors: [algorithmNotAllowed(alg)], - data: undefined, - }; + return failedValidation([algorithmNotAllowed(alg)]); }; const validateDigest = ( digest?: object -): FlexibleValidationResult => { +): ValidationResult => { if (!digest) { - return { - errors: [digestClaimNotFound()], - data: undefined, - }; + return failedValidation([digestClaimNotFound()]); } const result = ClientAssertionDigest.safeParse(digest); if (!result.success) { - return { - errors: [invalidDigestFormat()], - data: undefined, - }; + return failedValidation([invalidDigestFormat()]); } const validatedDigest = result.data; const digestLengthError = @@ -284,32 +217,24 @@ const validateDigest = ( ? invalidHashAlgorithm() : undefined; if (!digestLengthError && !digestAlgError) { - return { - errors: undefined, - data: result.data, - }; + return successfulValidation(result.data); } - return { - errors: [digestLengthError, digestAlgError].filter( - (e) => e !== undefined - ) as Array>, - data: undefined, - }; + return failedValidation([digestLengthError, digestAlgError]); }; // eslint-disable-next-line complexity export const verifyClientAssertion = ( clientAssertionJws: string, clientId: string | undefined -): ValidationResult => { +): ValidationResult => { try { const decoded = decode(clientAssertionJws, { complete: true, json: true }); if (!decoded) { - return { errors: [invalidClientAssertionFormat()], data: undefined }; + return failedValidation([invalidClientAssertionFormat()]); } if (typeof decoded.payload === "string") { - return { errors: [unexpectedClientAssertionPayload()], data: undefined }; + return failedValidation([unexpectedClientAssertionPayload()]); } const { errors: jtiErrors, data: validatedJti } = validateJti( @@ -370,25 +295,22 @@ export const verifyClientAssertion = ( digest: validatedDigest, }, }; - return { errors: undefined, data: result }; + return successfulValidation(result); } - return { - errors: [ - ...(jtiErrors || []), - ...(iatErrors || []), - ...(expErrors || []), - ...(issErrors || []), - ...(subErrors || []), - ...(purposeIdErrors || []), - ...(kidErrors || []), - ...(audErrors || []), - ...(algErrors || []), - ...(digestErrors || []), - ], - data: undefined, - }; + return failedValidation([ + ...(jtiErrors || []), + ...(iatErrors || []), + ...(expErrors || []), + ...(issErrors || []), + ...(subErrors || []), + ...(purposeIdErrors || []), + ...(kidErrors || []), + ...(audErrors || []), + ...(algErrors || []), + ...(digestErrors || []), + ]); } catch (error) { - return { errors: [unexpectedClientAssertionPayload()], data: undefined }; + return failedValidation([unexpectedClientAssertionPayload()]); } }; @@ -444,15 +366,12 @@ export const validatePlatformState = ( export const validateClientKindAndPlatformState = ( key: ApiKey | ConsumerKey, jwt: ClientAssertion -): ValidationResult => +): ValidationResult => match(key.clientKind) .with(clientKindTokenStates.api, () => ApiKey.safeParse(key).success - ? { data: jwt, errors: undefined } - : { - errors: [unexpectedKeyType(clientKindTokenStates.api)], - data: undefined, - } + ? successfulValidation(jwt) + : failedValidation([unexpectedKeyType(clientKindTokenStates.api)]) ) .with(clientKindTokenStates.consumer, () => { if (ConsumerKey.safeParse(key).success) { @@ -462,18 +381,24 @@ export const validateClientKindAndPlatformState = ( : purposeIdNotProvided(); if (platformStateErrors.length === 0 && !purposeIdError) { - return { errors: undefined, data: jwt }; + return successfulValidation(jwt); } - return { - errors: [...platformStateErrors, purposeIdError].filter( - (e) => e !== undefined - ) as Array>, - data: undefined, - }; + return failedValidation([...platformStateErrors, purposeIdError]); } - return { - errors: [unexpectedKeyType(clientKindTokenStates.consumer)], - data: undefined, - }; + return failedValidation([ + unexpectedKeyType(clientKindTokenStates.consumer), + ]); }) .exhaustive(); + +const successfulValidation = (result: T): SuccessfulValidation => ({ + data: result, + errors: undefined, +}); + +const failedValidation = ( + errors: Array | undefined> +): FailedValidation => ({ + data: undefined, + errors: errors.filter((e) => e !== undefined) as Array>, +}); diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index 066e409662..f7ceff1abb 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -5,12 +5,17 @@ import { verifyClientAssertion, validateClientKindAndPlatformState, } from "./utils.js"; -import { ApiKey, ConsumerKey, ValidationResult } from "./types.js"; +import { + ApiKey, + ClientAssertion, + ConsumerKey, + ValidationResult, +} from "./types.js"; export const validateClientAssertion = async ( request: authorizationServerApi.AccessTokenRequest, key: ConsumerKey | ApiKey // TODO use just Key? -): Promise => { +): Promise> => { const parametersErrors = validateRequestParameters(request); const { errors: clientAssertionVerificationErrors, data: jwt } = From e2e50a3b0d927e66cb288a6c18feace8ca397cbf Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 12 Sep 2024 15:47:12 +0200 Subject: [PATCH 178/241] Add tests --- .../test/validation.test.ts | 52 +++++++++++++++++-- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/packages/client-assertion-validation/test/validation.test.ts b/packages/client-assertion-validation/test/validation.test.ts index 0a3d009a70..3e9f53a08c 100644 --- a/packages/client-assertion-validation/test/validation.test.ts +++ b/packages/client-assertion-validation/test/validation.test.ts @@ -60,7 +60,9 @@ import { describe("validation test", () => { describe("validateRequestParameters", () => { it("success request parameters", () => { - expect(1).toBe(1); + const request = getMockAccessTokenRequest(); + const errors = validateRequestParameters(request); + expect(errors).toBeUndefined(); }); it("invalidAssertionType", () => { @@ -90,7 +92,13 @@ describe("validation test", () => { describe("verifyClientAssertion", () => { it("success client assertion", () => { - expect(1).toBe(1); + const a = getMockClientAssertion({ + customHeader: {}, + payload: {}, + customClaims: {}, + }); + const { errors } = verifyClientAssertion(a, undefined); + expect(errors).toBeUndefined(); }); it("invalidAudienceFormat", () => { @@ -390,7 +398,34 @@ describe("validation test", () => { describe("verifyClientAssertionSignature", () => { it("success client assertion signature", () => { - expect(1).toBe(1); + const threeHourLater = new Date(); + threeHourLater.setHours(threeHourLater.getHours() + 3); + + const keySet = crypto.generateKeyPairSync("rsa", { + modulusLength: 2048, + }); + + const jws = getMockClientAssertion({ + customHeader: {}, + payload: { + iat: new Date().getTime() / 1000, + exp: threeHourLater.getTime() / 1000, + }, + customClaims: {}, + keySet, + }); + const publicKey = keySet.publicKey + .export({ + type: "pkcs1", + format: "pem", + }) + .toString(); + const mockConsumerKey = { + ...getMockConsumerKey(), + publicKey, + }; + const errors = verifyClientAssertionSignature(jws, mockConsumerKey); + expect(errors).toBeUndefined(); }); it.skip("invalidClientAssertionSignatureType", () => { @@ -484,7 +519,16 @@ describe("validation test", () => { describe("validatePlatformState", () => { it("success", () => { - expect(1).toBe(1); + const mockKey: ConsumerKey = { + ...getMockConsumerKey(), + agreementState: itemState.active, + descriptorState: itemState.active, + purposeState: itemState.active, + }; + validatePlatformState(mockKey); + const errors = validatePlatformState(mockKey); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(0); }); it("inactiveAgreement", () => { From 8b163040ade9f5f6aba0c23ec13e902ec14c2a7a Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Thu, 12 Sep 2024 15:49:28 +0200 Subject: [PATCH 179/241] Remove comment --- packages/client-assertion-validation/src/types.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/client-assertion-validation/src/types.ts b/packages/client-assertion-validation/src/types.ts index 25f8e4683c..1c32138845 100644 --- a/packages/client-assertion-validation/src/types.ts +++ b/packages/client-assertion-validation/src/types.ts @@ -83,9 +83,3 @@ export type FailedValidation = { errors: Array>; data: undefined; }; - -// // alternatively -// export type ValidationResult2 = { -// errors: Array>; -// data?: ClientAssertion; -// }; From 4a44eb040db78be71eae5d56a5fe5253ef08c520 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 13 Sep 2024 09:38:32 +0200 Subject: [PATCH 180/241] Refactor --- .../client-assertion-validation/src/utils.ts | 76 +++++++++---------- .../src/validation.ts | 8 +- .../test/validation.test.ts | 32 ++++---- 3 files changed, 58 insertions(+), 58 deletions(-) diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index d9fbc4fec1..1256af7971 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -2,6 +2,7 @@ import { authorizationServerApi } from "pagopa-interop-api-clients"; import { decode, JsonWebTokenError, + JwtPayload, NotBeforeError, TokenExpiredError, verify, @@ -69,7 +70,7 @@ const ALLOWED_DIGEST_ALGORITHM = "SHA256"; export const validateRequestParameters = ( request: authorizationServerApi.AccessTokenRequest -): Array> | undefined => { +): ValidationResult => { const assertionTypeError = request.client_assertion_type !== EXPECTED_CLIENT_ASSERTION_TYPE ? invalidAssertionType(request.client_assertion_type) @@ -82,43 +83,37 @@ export const validateRequestParameters = ( : undefined; if (!assertionTypeError && !grantTypeError) { - return undefined; + return successfulValidation(request); } - return [assertionTypeError, grantTypeError].filter( - (e) => e !== undefined - ) as Array>; + return failedValidation([assertionTypeError, grantTypeError]); }; const validateJti = (jti?: string): ValidationResult => { if (!jti) { return failedValidation([jtiNotFound()]); - } else { - return successfulValidation(jti); } + return successfulValidation(jti); }; const validateIat = (iat?: number): ValidationResult => { if (!iat) { return failedValidation([issuedAtNotFound()]); - } else { - return successfulValidation(iat); } + return successfulValidation(iat); }; const validateExp = (exp?: number): ValidationResult => { if (!exp) { return failedValidation([expNotFound()]); - } else { - return successfulValidation(exp); } + return successfulValidation(exp); }; const validateIss = (iss?: string): ValidationResult => { if (!iss) { return failedValidation([issuerNotFound()]); - } else { - return successfulValidation(iss); } + return successfulValidation(iss); }; const validateSub = ( @@ -151,12 +146,11 @@ const validatePurposeId = ( ): ValidationResult => { if (purposeId && !PurposeId.safeParse(purposeId).success) { return failedValidation([invalidPurposeIdClaimFormat(purposeId)]); - } else { - const validatedPurposeId = purposeId - ? unsafeBrandId(purposeId) - : undefined; - return successfulValidation(validatedPurposeId); } + const validatedPurposeId = purposeId + ? unsafeBrandId(purposeId) + : undefined; + return successfulValidation(validatedPurposeId); }; const validateKid = (kid?: string): ValidationResult => { @@ -179,12 +173,11 @@ const validateAudience = ( if (!Array.isArray(aud)) { return failedValidation([invalidAudienceFormat()]); - } else { - if (!aud.includes(CLIENT_ASSERTION_AUDIENCE)) { - return failedValidation([invalidAudience()]); - } - return successfulValidation(aud); } + if (!aud.includes(CLIENT_ASSERTION_AUDIENCE)) { + return failedValidation([invalidAudience()]); + } + return successfulValidation(aud); }; const validateAlgorithm = (alg?: string): ValidationResult => { @@ -320,7 +313,7 @@ export const verifyClientAssertion = ( export const verifyClientAssertionSignature = ( clientAssertionJws: string, key: Key -): Array> | undefined => { +): ValidationResult => { try { const result = verify(clientAssertionJws, key.publicKey, { algorithms: [key.algorithm], @@ -328,27 +321,28 @@ export const verifyClientAssertionSignature = ( // TODO: no idea when result is a string if (typeof result === "string") { - return [invalidClientAssertionSignatureType(typeof result)]; - } else { - return undefined; + return failedValidation([ + invalidClientAssertionSignatureType(typeof result), + ]); } + return successfulValidation(result); } catch (error: unknown) { if (error instanceof TokenExpiredError) { - return [tokenExpiredError()]; + return failedValidation([tokenExpiredError()]); } else if (error instanceof NotBeforeError) { - return [notBeforeError()]; + return failedValidation([notBeforeError()]); } else if (error instanceof JsonWebTokenError) { // TODO: this might overlap with invalidClientAssertionFormat raised inside verifyClientAssertion - return [jsonWebTokenError(error.message)]; + return failedValidation([jsonWebTokenError(error.message)]); } else { - return [clientAssertionSignatureVerificationFailure()]; + return failedValidation([clientAssertionSignatureVerificationFailure()]); } } }; export const validatePlatformState = ( key: ConsumerKey -): Array> => { +): ValidationResult => { const agreementError = key.agreementState !== "ACTIVE" ? inactiveAgreement() : undefined; @@ -358,9 +352,10 @@ export const validatePlatformState = ( const purposeError = key.purposeState !== "ACTIVE" ? inactivePurpose() : undefined; - return [agreementError, descriptorError, purposeError].filter( - (e) => e !== undefined - ) as Array>; + if (!agreementError && !descriptorError && !purposeError) { + return successfulValidation(key); + } + return failedValidation([agreementError, descriptorError, purposeError]); }; export const validateClientKindAndPlatformState = ( @@ -375,15 +370,20 @@ export const validateClientKindAndPlatformState = ( ) .with(clientKindTokenStates.consumer, () => { if (ConsumerKey.safeParse(key).success) { - const platformStateErrors = validatePlatformState(key as ConsumerKey); + const { errors: platformStateErrors } = validatePlatformState( + key as ConsumerKey + ); const purposeIdError = jwt.payload.purposeId ? undefined : purposeIdNotProvided(); - if (platformStateErrors.length === 0 && !purposeIdError) { + if (!platformStateErrors && !purposeIdError) { return successfulValidation(jwt); } - return failedValidation([...platformStateErrors, purposeIdError]); + return failedValidation([ + ...(platformStateErrors || []), + purposeIdError, + ]); } return failedValidation([ unexpectedKeyType(clientKindTokenStates.consumer), diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index f7ceff1abb..a2446cd516 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -16,15 +16,13 @@ export const validateClientAssertion = async ( request: authorizationServerApi.AccessTokenRequest, key: ConsumerKey | ApiKey // TODO use just Key? ): Promise> => { - const parametersErrors = validateRequestParameters(request); + const { errors: parametersErrors } = validateRequestParameters(request); const { errors: clientAssertionVerificationErrors, data: jwt } = verifyClientAssertion(request.client_assertion, request.client_id); - const clientAssertionSignatureErrors = verifyClientAssertionSignature( - request.client_assertion, - key - ); + const { errors: clientAssertionSignatureErrors } = + verifyClientAssertionSignature(request.client_assertion, key); if ( parametersErrors || diff --git a/packages/client-assertion-validation/test/validation.test.ts b/packages/client-assertion-validation/test/validation.test.ts index 3e9f53a08c..d0b512d6eb 100644 --- a/packages/client-assertion-validation/test/validation.test.ts +++ b/packages/client-assertion-validation/test/validation.test.ts @@ -61,7 +61,7 @@ describe("validation test", () => { describe("validateRequestParameters", () => { it("success request parameters", () => { const request = getMockAccessTokenRequest(); - const errors = validateRequestParameters(request); + const { errors } = validateRequestParameters(request); expect(errors).toBeUndefined(); }); @@ -71,7 +71,7 @@ describe("validation test", () => { ...getMockAccessTokenRequest(), client_assertion_type: wrongAssertionType, }; - const errors = validateRequestParameters(request); + const { errors } = validateRequestParameters(request); expect(errors).toBeDefined(); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(invalidAssertionType(wrongAssertionType)); @@ -424,7 +424,7 @@ describe("validation test", () => { ...getMockConsumerKey(), publicKey, }; - const errors = verifyClientAssertionSignature(jws, mockConsumerKey); + const { errors } = verifyClientAssertionSignature(jws, mockConsumerKey); expect(errors).toBeUndefined(); }); @@ -462,14 +462,17 @@ describe("validation test", () => { ...getMockConsumerKey(), publicKey, }; - const errors = verifyClientAssertionSignature(jws, mockConsumerKey); + const { errors } = verifyClientAssertionSignature(jws, mockConsumerKey); expect(errors).toBeDefined(); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(tokenExpiredError()); }); it("jsonWebTokenError", () => { const mockKey = getMockConsumerKey(); - const errors = verifyClientAssertionSignature("not-a-valid-jws", mockKey); + const { errors } = verifyClientAssertionSignature( + "not-a-valid-jws", + mockKey + ); expect(errors).toBeDefined(); expect(errors).toHaveLength(1); expect(errors![0].title).toEqual(jsonWebTokenError("").title); @@ -506,7 +509,7 @@ describe("validation test", () => { publicKey, }; - const errors = verifyClientAssertionSignature(jws, mockConsumerKey); + const { errors } = verifyClientAssertionSignature(jws, mockConsumerKey); expect(errors).toBeDefined(); expect(errors).toHaveLength(1); expect(errors![0]).toEqual(notBeforeError()); @@ -526,9 +529,8 @@ describe("validation test", () => { purposeState: itemState.active, }; validatePlatformState(mockKey); - const errors = validatePlatformState(mockKey); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(0); + const { errors } = validatePlatformState(mockKey); + expect(errors).toBeUndefined(); }); it("inactiveAgreement", () => { @@ -537,11 +539,11 @@ describe("validation test", () => { agreementState: itemState.inactive, }; validatePlatformState(mockKey); - const errors = validatePlatformState(mockKey); + const { errors } = validatePlatformState(mockKey); expect(errors).toBeDefined(); expect(errors).toHaveLength(1); - expect(errors[0]).toEqual(inactiveAgreement()); + expect(errors![0]).toEqual(inactiveAgreement()); }); it("inactiveAgreement", () => { const mockKey: ConsumerKey = { @@ -549,11 +551,11 @@ describe("validation test", () => { descriptorState: itemState.inactive, }; validatePlatformState(mockKey); - const errors = validatePlatformState(mockKey); + const { errors } = validatePlatformState(mockKey); expect(errors).toBeDefined(); expect(errors).toHaveLength(1); - expect(errors[0]).toEqual(inactiveEService()); + expect(errors![0]).toEqual(inactiveEService()); }); it("inactivePurpose", () => { const mockKey: ConsumerKey = { @@ -561,11 +563,11 @@ describe("validation test", () => { purposeState: itemState.inactive, }; validatePlatformState(mockKey); - const errors = validatePlatformState(mockKey); + const { errors } = validatePlatformState(mockKey); expect(errors).toBeDefined(); expect(errors).toHaveLength(1); - expect(errors[0]).toEqual(inactivePurpose()); + expect(errors![0]).toEqual(inactivePurpose()); }); }); From 8519e6040d8d7b1d50769ae278bdd7417f422dbf Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 13 Sep 2024 10:07:21 +0200 Subject: [PATCH 181/241] Refactor --- .../client-assertion-validation/src/utils.ts | 46 ++++++++++++------- .../src/validation.ts | 23 ++++++---- 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index 1256af7971..22264d24bf 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -291,16 +291,16 @@ export const verifyClientAssertion = ( return successfulValidation(result); } return failedValidation([ - ...(jtiErrors || []), - ...(iatErrors || []), - ...(expErrors || []), - ...(issErrors || []), - ...(subErrors || []), - ...(purposeIdErrors || []), - ...(kidErrors || []), - ...(audErrors || []), - ...(algErrors || []), - ...(digestErrors || []), + jtiErrors, + iatErrors, + expErrors, + issErrors, + subErrors, + purposeIdErrors, + kidErrors, + audErrors, + algErrors, + digestErrors, ]); } catch (error) { return failedValidation([unexpectedClientAssertionPayload()]); @@ -391,14 +391,26 @@ export const validateClientKindAndPlatformState = ( }) .exhaustive(); -const successfulValidation = (result: T): SuccessfulValidation => ({ +export const successfulValidation = ( + result: T +): SuccessfulValidation => ({ data: result, errors: undefined, }); -const failedValidation = ( - errors: Array | undefined> -): FailedValidation => ({ - data: undefined, - errors: errors.filter((e) => e !== undefined) as Array>, -}); +export const failedValidation = ( + // errors: [[error1, error2, undefined], error3, undefined] + errors: Array< + Array | undefined> | ApiError | undefined + > +): FailedValidation => { + const nestedArrayWithoutUndefined = errors.filter((a) => a !== undefined); + const flattenedArray = nestedArrayWithoutUndefined.flat(1); + const flattenedArrayWithoutUndefined = flattenedArray.filter( + (e) => e !== undefined + ); + return { + data: undefined, + errors: flattenedArrayWithoutUndefined, + }; +}; diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index a2446cd516..bae401aa6b 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -4,6 +4,8 @@ import { validateRequestParameters, verifyClientAssertion, validateClientKindAndPlatformState, + failedValidation, + successfulValidation, } from "./utils.js"; import { ApiKey, @@ -29,15 +31,18 @@ export const validateClientAssertion = async ( clientAssertionVerificationErrors || clientAssertionSignatureErrors ) { - return { - data: undefined, - errors: [ - ...(parametersErrors || []), - ...(clientAssertionVerificationErrors || []), - ...(clientAssertionSignatureErrors || []), - ], - }; + return failedValidation([ + parametersErrors, + clientAssertionVerificationErrors, + clientAssertionSignatureErrors, + ]); } + const { errors: clientKindAndPlatormStateErrors } = + validateClientKindAndPlatformState(key, jwt); - return validateClientKindAndPlatformState(key, jwt); + if (clientKindAndPlatormStateErrors) { + return failedValidation([clientAssertionSignatureErrors]); + } + + return successfulValidation(jwt); }; From 9a72065a0372647acc72e6b8c9086930ab0b7f61 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 13 Sep 2024 10:07:37 +0200 Subject: [PATCH 182/241] Add util tests --- .../test/utils.test.ts | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 packages/client-assertion-validation/test/utils.test.ts diff --git a/packages/client-assertion-validation/test/utils.test.ts b/packages/client-assertion-validation/test/utils.test.ts new file mode 100644 index 0000000000..edc65d471f --- /dev/null +++ b/packages/client-assertion-validation/test/utils.test.ts @@ -0,0 +1,49 @@ +import { describe, expect, it } from "vitest"; +import { inactiveAgreement, inactiveEService } from "../src/errors"; +import { failedValidation } from "../src/utils"; + +describe("failedValidation", () => { + it("array of errors", () => { + const errors = [inactiveEService(), inactiveAgreement()]; + const result = failedValidation(errors); + expect(result).toEqual({ + data: undefined, + errors: [inactiveEService(), inactiveAgreement()], + }); + }); + it("array of one error", () => { + const errors = [inactiveEService()]; + const result = failedValidation(errors); + expect(result).toEqual({ + data: undefined, + errors: [inactiveEService()], + }); + }); + it("array of errors or undefined", () => { + const errors = [inactiveEService(), inactiveAgreement(), undefined]; + const result = failedValidation(errors); + expect(result).toEqual({ + data: undefined, + errors: [inactiveEService(), inactiveAgreement()], + }); + }); + it("nested array of errors", () => { + const errors = [[inactiveEService(), inactiveAgreement()], undefined]; + const result = failedValidation(errors); + expect(result).toEqual({ + data: undefined, + errors: [inactiveEService(), inactiveAgreement()], + }); + }); + it("nested array of errors or undefined", () => { + const errors = [ + [inactiveEService(), inactiveAgreement(), undefined], + undefined, + ]; + const result = failedValidation(errors); + expect(result).toEqual({ + data: undefined, + errors: [inactiveEService(), inactiveAgreement()], + }); + }); +}); From f4e37aa4292c6a0d189f283d3d65631a317cec47 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 13 Sep 2024 10:09:32 +0200 Subject: [PATCH 183/241] Add test --- .../test/utils.test.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/client-assertion-validation/test/utils.test.ts b/packages/client-assertion-validation/test/utils.test.ts index edc65d471f..5f0efeaa00 100644 --- a/packages/client-assertion-validation/test/utils.test.ts +++ b/packages/client-assertion-validation/test/utils.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; import { inactiveAgreement, inactiveEService } from "../src/errors"; -import { failedValidation } from "../src/utils"; +import { failedValidation, successfulValidation } from "../src/utils"; describe("failedValidation", () => { it("array of errors", () => { @@ -47,3 +47,16 @@ describe("failedValidation", () => { }); }); }); + +describe("successfulValidation", () => { + it("string", () => { + const resultString = "result"; + const result = successfulValidation(resultString); + expect(result).toEqual({ data: resultString, errors: undefined }); + }); + it("number", () => { + const resultNumber = 1; + const result = successfulValidation(resultNumber); + expect(result).toEqual({ data: resultNumber, errors: undefined }); + }); +}); From 7a8205fc246063fed2be6759d266263107a50c25 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 13 Sep 2024 10:14:51 +0200 Subject: [PATCH 184/241] Fix --- packages/client-assertion-validation/src/utils.ts | 2 +- packages/client-assertion-validation/test/utils.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index 22264d24bf..9a1bf7965a 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -411,6 +411,6 @@ export const failedValidation = ( ); return { data: undefined, - errors: flattenedArrayWithoutUndefined, + errors: flattenedArrayWithoutUndefined as Array>, }; }; diff --git a/packages/client-assertion-validation/test/utils.test.ts b/packages/client-assertion-validation/test/utils.test.ts index 5f0efeaa00..522fc73f99 100644 --- a/packages/client-assertion-validation/test/utils.test.ts +++ b/packages/client-assertion-validation/test/utils.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { inactiveAgreement, inactiveEService } from "../src/errors"; -import { failedValidation, successfulValidation } from "../src/utils"; +import { inactiveAgreement, inactiveEService } from "../src/errors.js"; +import { failedValidation, successfulValidation } from "../src/utils.js"; describe("failedValidation", () => { it("array of errors", () => { From 5f62de089aaa0cd7c3d04e47df36915767f670ed Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Fri, 13 Sep 2024 10:17:05 +0200 Subject: [PATCH 185/241] Remove hardcoded string --- packages/client-assertion-validation/src/utils.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index 9a1bf7965a..5c802d5338 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -11,6 +11,7 @@ import { ApiError, ClientId, clientKindTokenStates, + itemState, PurposeId, unsafeBrandId, } from "pagopa-interop-models"; @@ -344,13 +345,13 @@ export const validatePlatformState = ( key: ConsumerKey ): ValidationResult => { const agreementError = - key.agreementState !== "ACTIVE" ? inactiveAgreement() : undefined; + key.agreementState !== itemState.active ? inactiveAgreement() : undefined; const descriptorError = - key.descriptorState !== "ACTIVE" ? inactiveEService() : undefined; + key.descriptorState !== itemState.active ? inactiveEService() : undefined; const purposeError = - key.purposeState !== "ACTIVE" ? inactivePurpose() : undefined; + key.purposeState !== itemState.active ? inactivePurpose() : undefined; if (!agreementError && !descriptorError && !purposeError) { return successfulValidation(key); From 7788383055fe06e0d15599d734f4a0678a206087 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 16 Sep 2024 11:25:41 +0200 Subject: [PATCH 186/241] Update comments --- packages/client-assertion-validation/src/errors.ts | 4 ++-- packages/client-assertion-validation/test/validation.test.ts | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/client-assertion-validation/src/errors.ts b/packages/client-assertion-validation/src/errors.ts index ed64b75eb6..70090c11e2 100644 --- a/packages/client-assertion-validation/src/errors.ts +++ b/packages/client-assertion-validation/src/errors.ts @@ -40,7 +40,7 @@ export const errorCodes = { export type ErrorCodes = keyof typeof errorCodes; -// TODO: missing errors: +// Notes about errors: // - InvalidClientIdFormat -> check on uuid // - ClientAssertionParseFailed -> already handled in invalidClientAssertionFormat X // - ClientAssertionInvalidClaims -> should be covered by individual checks ? @@ -56,7 +56,7 @@ export type ErrorCodes = keyof typeof errorCodes; // - PublicKeyParseFailed -> out of scope for this module X // - ClientAssertionVerificationError -> maybe too generic X // - InvalidClientAssertionSignature -> maybe already covered by existing cases X -// - PurposeIdNotProvided -> based on entry type (Api client doesn't need purposeId) // TODO: where to put this error? +// - PurposeIdNotProvided -> based on entry type (Api client doesn't need purposeId) // - PurposeNotFound -> related to previous, check if there is a purpose entry for that purposeId (in platform states) needed in this package? // - InvalidKidFormat -> Verify that kid does not contain special characters diff --git a/packages/client-assertion-validation/test/validation.test.ts b/packages/client-assertion-validation/test/validation.test.ts index d0b512d6eb..f4d91aa8e8 100644 --- a/packages/client-assertion-validation/test/validation.test.ts +++ b/packages/client-assertion-validation/test/validation.test.ts @@ -689,7 +689,6 @@ describe("validation test", () => { }); it("purposeIdNotProvided", () => { - // TODO this should be related to the case of consumerKey const mockConsumerKey = getMockConsumerKey(); const { data: mockClientAssertion } = verifyClientAssertion( getMockClientAssertion({ From c27e3adb373048429a214744c666ae484abee186 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 16 Sep 2024 14:19:46 +0200 Subject: [PATCH 187/241] Fix --- packages/client-assertion-validation/src/utils.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index 5c802d5338..7a93d5c520 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -381,10 +381,7 @@ export const validateClientKindAndPlatformState = ( if (!platformStateErrors && !purposeIdError) { return successfulValidation(jwt); } - return failedValidation([ - ...(platformStateErrors || []), - purposeIdError, - ]); + return failedValidation([platformStateErrors, purposeIdError]); } return failedValidation([ unexpectedKeyType(clientKindTokenStates.consumer), From ba9609fa84dbfd453957b02da30ea4731dd59b1e Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 16 Sep 2024 15:54:39 +0200 Subject: [PATCH 188/241] Refactor --- .../client-assertion-validation/src/types.ts | 7 ++++--- .../src/validation.ts | 19 +++++++++++++++++-- .../client-assertion-validation/test/utils.ts | 11 ++++++----- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/packages/client-assertion-validation/src/types.ts b/packages/client-assertion-validation/src/types.ts index 1c32138845..20c090d9c1 100644 --- a/packages/client-assertion-validation/src/types.ts +++ b/packages/client-assertion-validation/src/types.ts @@ -51,9 +51,10 @@ export type ClientAssertion = z.infer; export const Key = z .object({ - GSIPK_clientId: ClientId, + clientId: ClientId, consumerId: TenantId, - kidWithPurposeId: z.string(), // TODO which field of the table is mapped to this? + kid: z.string(), + purposeId: PurposeId, // TODO which field of the table is mapped to this? publicKey: z.string().min(1), algorithm: z.literal("RS256"), // no field to map from the table. Is it extracted from publicKey field? }) @@ -62,7 +63,7 @@ export type Key = z.infer; export const ConsumerKey = Key.extend({ clientKind: z.literal(clientKindTokenStates.consumer), - GSIPK_purposeId: PurposeId, // TODO is this naming ok? + purposeId: PurposeId, // TODO is this naming ok? purposeState: authorizationManagementApi.ClientComponentState, agreementId: AgreementId, agreementState: authorizationManagementApi.ClientComponentState, diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index bae401aa6b..aa30878a94 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -1,4 +1,4 @@ -import { authorizationServerApi } from "pagopa-interop-api-clients"; +import { z } from "zod"; import { verifyClientAssertionSignature, validateRequestParameters, @@ -15,7 +15,7 @@ import { } from "./types.js"; export const validateClientAssertion = async ( - request: authorizationServerApi.AccessTokenRequest, + request: ClientAssertionValidationRequest, key: ConsumerKey | ApiKey // TODO use just Key? ): Promise> => { const { errors: parametersErrors } = validateRequestParameters(request); @@ -46,3 +46,18 @@ export const validateClientAssertion = async ( return successfulValidation(jwt); }; + +const EXPECTED_CLIENT_ASSERTION_TYPE = + "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; // TODO: env? +const EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE = "client_credentials"; // TODO: env? + +export const ClientAssertionValidationRequest = z.object({ + client_id: z.optional(z.string().uuid()), + client_assertion: z.string(), + client_assertion_type: z.literal(EXPECTED_CLIENT_ASSERTION_TYPE), + grant_type: z.literal(EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE), +}); + +export type ClientAssertionValidationRequest = z.infer< + typeof ClientAssertionValidationRequest +>; diff --git a/packages/client-assertion-validation/test/utils.ts b/packages/client-assertion-validation/test/utils.ts index 78985dae91..22e96fee17 100644 --- a/packages/client-assertion-validation/test/utils.ts +++ b/packages/client-assertion-validation/test/utils.ts @@ -62,13 +62,13 @@ export const getMockClientAssertion = ({ }; export const getMockConsumerKey = (): ConsumerKey => ({ - GSIPK_clientId: generateId(), + clientId: generateId(), consumerId: generateId(), - kidWithPurposeId: "", + kid: "", + purposeId: generateId(), publicKey: "TODO", algorithm: "RS256", clientKind: clientKindTokenStates.consumer, - GSIPK_purposeId: generateId(), purposeState: itemState.active, agreementId: generateId(), agreementState: itemState.active, @@ -77,9 +77,10 @@ export const getMockConsumerKey = (): ConsumerKey => ({ }); export const getMockApiKey = (): ApiKey => ({ - GSIPK_clientId: generateId(), + clientId: generateId(), consumerId: generateId(), - kidWithPurposeId: "", + kid: "", + purposeId: generateId(), publicKey: "TODO", algorithm: "RS256", clientKind: clientKindTokenStates.api, From f9ba9dbb9093ffbbe3e6364fb955522e8fd47865 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Mon, 16 Sep 2024 16:11:35 +0200 Subject: [PATCH 189/241] Refactor --- .../client-assertion-validation/src/index.ts | 1 + .../client-assertion-validation/src/types.ts | 15 +++++++++++++++ .../client-assertion-validation/src/utils.ts | 4 ++-- .../src/validation.ts | 17 +---------------- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/packages/client-assertion-validation/src/index.ts b/packages/client-assertion-validation/src/index.ts index ecd7a679ec..293a920555 100644 --- a/packages/client-assertion-validation/src/index.ts +++ b/packages/client-assertion-validation/src/index.ts @@ -1 +1,2 @@ export * from "./validation.js"; +export * from "./types.js"; diff --git a/packages/client-assertion-validation/src/types.ts b/packages/client-assertion-validation/src/types.ts index 20c090d9c1..02a6b0e783 100644 --- a/packages/client-assertion-validation/src/types.ts +++ b/packages/client-assertion-validation/src/types.ts @@ -10,6 +10,10 @@ import { } from "pagopa-interop-models"; import { z } from "zod"; import { ErrorCodes } from "./errors.js"; +import { + EXPECTED_CLIENT_ASSERTION_TYPE, + EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE, +} from "./utils.js"; export const ClientAssertionDigest = z .object({ @@ -84,3 +88,14 @@ export type FailedValidation = { errors: Array>; data: undefined; }; + +export const ClientAssertionValidationRequest = z.object({ + client_id: z.optional(z.string().uuid()), + client_assertion: z.string(), + client_assertion_type: z.literal(EXPECTED_CLIENT_ASSERTION_TYPE), + grant_type: z.literal(EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE), +}); + +export type ClientAssertionValidationRequest = z.infer< + typeof ClientAssertionValidationRequest +>; diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index 7a93d5c520..345ec6c904 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -63,9 +63,9 @@ import { purposeIdNotProvided, } from "./errors.js"; const CLIENT_ASSERTION_AUDIENCE = "test.interop.pagopa.it"; // TODO: env? -const EXPECTED_CLIENT_ASSERTION_TYPE = +export const EXPECTED_CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; // TODO: env? -const EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE = "client_credentials"; // TODO: env? +export const EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE = "client_credentials"; // TODO: env? const ALLOWED_ALGORITHM = "RS256"; const ALLOWED_DIGEST_ALGORITHM = "SHA256"; diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index aa30878a94..0fb5c9e6ba 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -1,4 +1,3 @@ -import { z } from "zod"; import { verifyClientAssertionSignature, validateRequestParameters, @@ -10,6 +9,7 @@ import { import { ApiKey, ClientAssertion, + ClientAssertionValidationRequest, ConsumerKey, ValidationResult, } from "./types.js"; @@ -46,18 +46,3 @@ export const validateClientAssertion = async ( return successfulValidation(jwt); }; - -const EXPECTED_CLIENT_ASSERTION_TYPE = - "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; // TODO: env? -const EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE = "client_credentials"; // TODO: env? - -export const ClientAssertionValidationRequest = z.object({ - client_id: z.optional(z.string().uuid()), - client_assertion: z.string(), - client_assertion_type: z.literal(EXPECTED_CLIENT_ASSERTION_TYPE), - grant_type: z.literal(EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE), -}); - -export type ClientAssertionValidationRequest = z.infer< - typeof ClientAssertionValidationRequest ->; From 9bdea36b1e6cc3fb97e67db3e0421e756e4d2a06 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 17 Sep 2024 14:56:04 +0200 Subject: [PATCH 190/241] Renaming --- ...atalogPlatformstateWriter.integration.test.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 23cbc82c4a..5fda2329ab 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -239,12 +239,12 @@ describe("integration tests", async () => { await readCatalogEntry(primaryKey, dynamoDBClient) ).toBeUndefined(); await writeCatalogEntry(catalogStateEntry, dynamoDBClient); - const expectedCatalogEntry = await readCatalogEntry( + const retrievedCatalogEntry = await readCatalogEntry( primaryKey, dynamoDBClient ); - expect(expectedCatalogEntry).toEqual(catalogStateEntry); + expect(retrievedCatalogEntry).toEqual(catalogStateEntry); }); }); @@ -271,12 +271,12 @@ describe("integration tests", async () => { updatedAt: new Date().toISOString(), }; await writeCatalogEntry(previousCatalogStateEntry, dynamoDBClient); - const expectedCatalogEntry = await readCatalogEntry( + const retrievedCatalogEntry = await readCatalogEntry( primaryKey, dynamoDBClient ); - expect(expectedCatalogEntry).toEqual(previousCatalogStateEntry); + expect(retrievedCatalogEntry).toEqual(previousCatalogStateEntry); }); }); @@ -305,11 +305,11 @@ describe("integration tests", async () => { }; await writeCatalogEntry(previousCatalogStateEntry, dynamoDBClient); await deleteCatalogEntry(primaryKey, dynamoDBClient); - const expectedCatalogEntry = await readCatalogEntry( + const retrievedCatalogEntry = await readCatalogEntry( primaryKey, dynamoDBClient ); - expect(expectedCatalogEntry).toBeUndefined(); + expect(retrievedCatalogEntry).toBeUndefined(); }); }); @@ -377,13 +377,13 @@ describe("integration tests", async () => { GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; await writeTokenStateEntry(tokenStateEntry, dynamoDBClient); - const expectedTokenStateEntries = + const retrievedTokenStateEntries = await readTokenStateEntriesByEserviceIdAndDescriptorId( eserviceId_descriptorId, dynamoDBClient ); - expect(expectedTokenStateEntries).toEqual([tokenStateEntry]); + expect(retrievedTokenStateEntries).toEqual([tokenStateEntry]); }); }); From 7d5e486d35dbb0568f48962dbfc168f0ee85e7bd Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 18 Sep 2024 11:41:25 +0200 Subject: [PATCH 191/241] Refactor --- .../client-assertion-validation/src/utils.ts | 217 +----------------- .../src/validation.ts | 214 ++++++++++++++++- 2 files changed, 220 insertions(+), 211 deletions(-) diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index 345ec6c904..c11dcd61ab 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -1,55 +1,32 @@ -import { authorizationServerApi } from "pagopa-interop-api-clients"; -import { - decode, - JsonWebTokenError, - JwtPayload, - NotBeforeError, - TokenExpiredError, - verify, -} from "jsonwebtoken"; import { ApiError, ClientId, - clientKindTokenStates, itemState, PurposeId, unsafeBrandId, } from "pagopa-interop-models"; -import { match } from "ts-pattern"; import { - ApiKey, - ClientAssertion, ClientAssertionDigest, ConsumerKey, FailedValidation, ValidationResult, - Key, SuccessfulValidation, } from "./types.js"; import { ErrorCodes, expNotFound, issuedAtNotFound, - invalidAssertionType, invalidAudience, invalidAudienceFormat, - invalidClientAssertionFormat, - invalidGrantType, issuerNotFound, jtiNotFound, subjectNotFound, - unexpectedClientAssertionPayload, invalidSubject, invalidPurposeIdClaimFormat, kidNotFound, inactiveAgreement, inactiveEService, inactivePurpose, - tokenExpiredError, - jsonWebTokenError, - notBeforeError, - clientAssertionSignatureVerificationFailure, - invalidClientAssertionSignatureType, invalidClientIdFormat, invalidSubjectFormat, algorithmNotFound, @@ -59,8 +36,6 @@ import { invalidHashLength, invalidHashAlgorithm, invalidKidFormat, - unexpectedKeyType, - purposeIdNotProvided, } from "./errors.js"; const CLIENT_ASSERTION_AUDIENCE = "test.interop.pagopa.it"; // TODO: env? export const EXPECTED_CLIENT_ASSERTION_TYPE = @@ -69,55 +44,35 @@ export const EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE = "client_credentials"; // T const ALLOWED_ALGORITHM = "RS256"; const ALLOWED_DIGEST_ALGORITHM = "SHA256"; -export const validateRequestParameters = ( - request: authorizationServerApi.AccessTokenRequest -): ValidationResult => { - const assertionTypeError = - request.client_assertion_type !== EXPECTED_CLIENT_ASSERTION_TYPE - ? invalidAssertionType(request.client_assertion_type) - : undefined; - - // TODO: this might be useless because authorizationServerApi.AccessTokenRequest has the string hard coded - const grantTypeError = - request.grant_type !== EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE - ? invalidGrantType(request.grant_type) - : undefined; - - if (!assertionTypeError && !grantTypeError) { - return successfulValidation(request); - } - return failedValidation([assertionTypeError, grantTypeError]); -}; - -const validateJti = (jti?: string): ValidationResult => { +export const validateJti = (jti?: string): ValidationResult => { if (!jti) { return failedValidation([jtiNotFound()]); } return successfulValidation(jti); }; -const validateIat = (iat?: number): ValidationResult => { +export const validateIat = (iat?: number): ValidationResult => { if (!iat) { return failedValidation([issuedAtNotFound()]); } return successfulValidation(iat); }; -const validateExp = (exp?: number): ValidationResult => { +export const validateExp = (exp?: number): ValidationResult => { if (!exp) { return failedValidation([expNotFound()]); } return successfulValidation(exp); }; -const validateIss = (iss?: string): ValidationResult => { +export const validateIss = (iss?: string): ValidationResult => { if (!iss) { return failedValidation([issuerNotFound()]); } return successfulValidation(iss); }; -const validateSub = ( +export const validateSub = ( sub?: string, clientId?: string ): ValidationResult => { @@ -142,7 +97,7 @@ const validateSub = ( return successfulValidation(sub); }; -const validatePurposeId = ( +export const validatePurposeId = ( purposeId?: string ): ValidationResult => { if (purposeId && !PurposeId.safeParse(purposeId).success) { @@ -154,7 +109,7 @@ const validatePurposeId = ( return successfulValidation(validatedPurposeId); }; -const validateKid = (kid?: string): ValidationResult => { +export const validateKid = (kid?: string): ValidationResult => { if (!kid) { return failedValidation([kidNotFound()]); } @@ -165,7 +120,7 @@ const validateKid = (kid?: string): ValidationResult => { return failedValidation([invalidKidFormat()]); }; -const validateAudience = ( +export const validateAudience = ( aud: string | string[] | undefined ): ValidationResult => { if (aud === CLIENT_ASSERTION_AUDIENCE) { @@ -181,7 +136,7 @@ const validateAudience = ( return successfulValidation(aud); }; -const validateAlgorithm = (alg?: string): ValidationResult => { +export const validateAlgorithm = (alg?: string): ValidationResult => { if (!alg) { return failedValidation([algorithmNotFound()]); } @@ -191,7 +146,7 @@ const validateAlgorithm = (alg?: string): ValidationResult => { return failedValidation([algorithmNotAllowed(alg)]); }; -const validateDigest = ( +export const validateDigest = ( digest?: object ): ValidationResult => { if (!digest) { @@ -216,131 +171,9 @@ const validateDigest = ( return failedValidation([digestLengthError, digestAlgError]); }; -// eslint-disable-next-line complexity -export const verifyClientAssertion = ( - clientAssertionJws: string, - clientId: string | undefined -): ValidationResult => { - try { - const decoded = decode(clientAssertionJws, { complete: true, json: true }); - if (!decoded) { - return failedValidation([invalidClientAssertionFormat()]); - } - - if (typeof decoded.payload === "string") { - return failedValidation([unexpectedClientAssertionPayload()]); - } - - const { errors: jtiErrors, data: validatedJti } = validateJti( - decoded.payload.jti - ); - const { errors: iatErrors, data: validatedIat } = validateIat( - decoded.payload.iat - ); - const { errors: expErrors, data: validatedExp } = validateExp( - decoded.payload.exp - ); - const { errors: issErrors, data: validatedIss } = validateIss( - decoded.payload.iss - ); - const { errors: subErrors, data: validatedSub } = validateSub( - decoded.payload.sub, - clientId - ); - const { errors: purposeIdErrors, data: validatedPurposeId } = - validatePurposeId(decoded.payload.purposeId); - const { errors: kidErrors, data: validatedKid } = validateKid( - decoded.header.kid - ); - const { errors: audErrors, data: validatedAud } = validateAudience( - decoded.payload.aud - ); - const { errors: algErrors, data: validatedAlg } = validateAlgorithm( - decoded.header.alg - ); - const { errors: digestErrors, data: validatedDigest } = validateDigest( - decoded.payload.digest - ); - if ( - !jtiErrors && - !iatErrors && - !expErrors && - !issErrors && - !subErrors && - !purposeIdErrors && - !kidErrors && - !audErrors && - !algErrors && - !digestErrors - ) { - const result: ClientAssertion = { - header: { - kid: validatedKid, - alg: validatedAlg, - }, - payload: { - sub: validatedSub, - purposeId: validatedPurposeId, - jti: validatedJti, - iat: validatedIat, - iss: validatedIss, - aud: validatedAud, - exp: validatedExp, - digest: validatedDigest, - }, - }; - return successfulValidation(result); - } - return failedValidation([ - jtiErrors, - iatErrors, - expErrors, - issErrors, - subErrors, - purposeIdErrors, - kidErrors, - audErrors, - algErrors, - digestErrors, - ]); - } catch (error) { - return failedValidation([unexpectedClientAssertionPayload()]); - } -}; - // export const b64Decode = (str: string): string => // Buffer.from(str, "base64").toString("binary"); -export const verifyClientAssertionSignature = ( - clientAssertionJws: string, - key: Key -): ValidationResult => { - try { - const result = verify(clientAssertionJws, key.publicKey, { - algorithms: [key.algorithm], - }); - - // TODO: no idea when result is a string - if (typeof result === "string") { - return failedValidation([ - invalidClientAssertionSignatureType(typeof result), - ]); - } - return successfulValidation(result); - } catch (error: unknown) { - if (error instanceof TokenExpiredError) { - return failedValidation([tokenExpiredError()]); - } else if (error instanceof NotBeforeError) { - return failedValidation([notBeforeError()]); - } else if (error instanceof JsonWebTokenError) { - // TODO: this might overlap with invalidClientAssertionFormat raised inside verifyClientAssertion - return failedValidation([jsonWebTokenError(error.message)]); - } else { - return failedValidation([clientAssertionSignatureVerificationFailure()]); - } - } -}; - export const validatePlatformState = ( key: ConsumerKey ): ValidationResult => { @@ -359,36 +192,6 @@ export const validatePlatformState = ( return failedValidation([agreementError, descriptorError, purposeError]); }; -export const validateClientKindAndPlatformState = ( - key: ApiKey | ConsumerKey, - jwt: ClientAssertion -): ValidationResult => - match(key.clientKind) - .with(clientKindTokenStates.api, () => - ApiKey.safeParse(key).success - ? successfulValidation(jwt) - : failedValidation([unexpectedKeyType(clientKindTokenStates.api)]) - ) - .with(clientKindTokenStates.consumer, () => { - if (ConsumerKey.safeParse(key).success) { - const { errors: platformStateErrors } = validatePlatformState( - key as ConsumerKey - ); - const purposeIdError = jwt.payload.purposeId - ? undefined - : purposeIdNotProvided(); - - if (!platformStateErrors && !purposeIdError) { - return successfulValidation(jwt); - } - return failedValidation([platformStateErrors, purposeIdError]); - } - return failedValidation([ - unexpectedKeyType(clientKindTokenStates.consumer), - ]); - }) - .exhaustive(); - export const successfulValidation = ( result: T ): SuccessfulValidation => ({ diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index 0fb5c9e6ba..474b60c921 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -1,18 +1,52 @@ +import { authorizationServerApi } from "pagopa-interop-api-clients"; +import { + decode, + JsonWebTokenError, + JwtPayload, + NotBeforeError, + TokenExpiredError, + verify, +} from "jsonwebtoken"; +import { match } from "ts-pattern"; +import { clientKindTokenStates } from "pagopa-interop-models"; import { - verifyClientAssertionSignature, - validateRequestParameters, - verifyClientAssertion, - validateClientKindAndPlatformState, failedValidation, successfulValidation, + EXPECTED_CLIENT_ASSERTION_TYPE, + EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE, + validateJti, + validateAlgorithm, + validateAudience, + validateDigest, + validateExp, + validateIat, + validateIss, + validateKid, + validatePurposeId, + validateSub, + validatePlatformState, } from "./utils.js"; import { ApiKey, ClientAssertion, ClientAssertionValidationRequest, ConsumerKey, + Key, ValidationResult, } from "./types.js"; +import { + clientAssertionSignatureVerificationFailure, + invalidAssertionType, + invalidClientAssertionFormat, + invalidClientAssertionSignatureType, + invalidGrantType, + jsonWebTokenError, + notBeforeError, + purposeIdNotProvided, + tokenExpiredError, + unexpectedClientAssertionPayload, + unexpectedKeyType, +} from "./errors.js"; export const validateClientAssertion = async ( request: ClientAssertionValidationRequest, @@ -46,3 +80,175 @@ export const validateClientAssertion = async ( return successfulValidation(jwt); }; + +export const validateRequestParameters = ( + request: authorizationServerApi.AccessTokenRequest +): ValidationResult => { + const assertionTypeError = + request.client_assertion_type !== EXPECTED_CLIENT_ASSERTION_TYPE + ? invalidAssertionType(request.client_assertion_type) + : undefined; + + // TODO: this might be useless because authorizationServerApi.AccessTokenRequest has the string hard coded + const grantTypeError = + request.grant_type !== EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE + ? invalidGrantType(request.grant_type) + : undefined; + + if (!assertionTypeError && !grantTypeError) { + return successfulValidation(request); + } + return failedValidation([assertionTypeError, grantTypeError]); +}; + +// eslint-disable-next-line complexity +export const verifyClientAssertion = ( + clientAssertionJws: string, + clientId: string | undefined +): ValidationResult => { + try { + const decoded = decode(clientAssertionJws, { complete: true, json: true }); + if (!decoded) { + return failedValidation([invalidClientAssertionFormat()]); + } + + if (typeof decoded.payload === "string") { + return failedValidation([unexpectedClientAssertionPayload()]); + } + + const { errors: jtiErrors, data: validatedJti } = validateJti( + decoded.payload.jti + ); + const { errors: iatErrors, data: validatedIat } = validateIat( + decoded.payload.iat + ); + const { errors: expErrors, data: validatedExp } = validateExp( + decoded.payload.exp + ); + const { errors: issErrors, data: validatedIss } = validateIss( + decoded.payload.iss + ); + const { errors: subErrors, data: validatedSub } = validateSub( + decoded.payload.sub, + clientId + ); + const { errors: purposeIdErrors, data: validatedPurposeId } = + validatePurposeId(decoded.payload.purposeId); + const { errors: kidErrors, data: validatedKid } = validateKid( + decoded.header.kid + ); + const { errors: audErrors, data: validatedAud } = validateAudience( + decoded.payload.aud + ); + const { errors: algErrors, data: validatedAlg } = validateAlgorithm( + decoded.header.alg + ); + const { errors: digestErrors, data: validatedDigest } = validateDigest( + decoded.payload.digest + ); + if ( + !jtiErrors && + !iatErrors && + !expErrors && + !issErrors && + !subErrors && + !purposeIdErrors && + !kidErrors && + !audErrors && + !algErrors && + !digestErrors + ) { + const result: ClientAssertion = { + header: { + kid: validatedKid, + alg: validatedAlg, + }, + payload: { + sub: validatedSub, + purposeId: validatedPurposeId, + jti: validatedJti, + iat: validatedIat, + iss: validatedIss, + aud: validatedAud, + exp: validatedExp, + digest: validatedDigest, + }, + }; + return successfulValidation(result); + } + return failedValidation([ + jtiErrors, + iatErrors, + expErrors, + issErrors, + subErrors, + purposeIdErrors, + kidErrors, + audErrors, + algErrors, + digestErrors, + ]); + } catch (error) { + return failedValidation([unexpectedClientAssertionPayload()]); + } +}; + +export const verifyClientAssertionSignature = ( + clientAssertionJws: string, + key: Key +): ValidationResult => { + try { + const result = verify(clientAssertionJws, key.publicKey, { + algorithms: [key.algorithm], + }); + + // TODO: no idea when result is a string + if (typeof result === "string") { + return failedValidation([ + invalidClientAssertionSignatureType(typeof result), + ]); + } + return successfulValidation(result); + } catch (error: unknown) { + if (error instanceof TokenExpiredError) { + return failedValidation([tokenExpiredError()]); + } else if (error instanceof NotBeforeError) { + return failedValidation([notBeforeError()]); + } else if (error instanceof JsonWebTokenError) { + // TODO: this might overlap with invalidClientAssertionFormat raised inside verifyClientAssertion + return failedValidation([jsonWebTokenError(error.message)]); + } else { + return failedValidation([clientAssertionSignatureVerificationFailure()]); + } + } +}; + +export const validateClientKindAndPlatformState = ( + key: ApiKey | ConsumerKey, + jwt: ClientAssertion +): ValidationResult => + match(key.clientKind) + .with(clientKindTokenStates.api, () => + ApiKey.safeParse(key).success + ? successfulValidation(jwt) + : failedValidation([unexpectedKeyType(clientKindTokenStates.api)]) + ) + .with(clientKindTokenStates.consumer, () => { + if (ConsumerKey.safeParse(key).success) { + const { errors: platformStateErrors } = validatePlatformState( + key as ConsumerKey + ); + const purposeIdError = jwt.payload.purposeId + ? undefined + : purposeIdNotProvided(); + + if (!platformStateErrors && !purposeIdError) { + return successfulValidation(jwt); + } + return failedValidation([platformStateErrors, purposeIdError]); + } + return failedValidation([ + unexpectedKeyType(clientKindTokenStates.consumer), + ]); + }) + .exhaustive(); From 519870bd487a31ed9182efe24219df9afb654dfa Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 18 Sep 2024 11:57:50 +0200 Subject: [PATCH 192/241] Fix tests --- packages/client-assertion-validation/test/validation.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client-assertion-validation/test/validation.test.ts b/packages/client-assertion-validation/test/validation.test.ts index f4d91aa8e8..93b3909525 100644 --- a/packages/client-assertion-validation/test/validation.test.ts +++ b/packages/client-assertion-validation/test/validation.test.ts @@ -12,11 +12,11 @@ import { import * as jwt from "jsonwebtoken"; import { validateClientKindAndPlatformState, - validatePlatformState, validateRequestParameters, verifyClientAssertion, verifyClientAssertionSignature, -} from "../src/utils.js"; +} from "../src/validation.js"; +import { validatePlatformState } from "../src/utils.js"; import { algorithmNotAllowed, algorithmNotFound, From bf361286047bbd43c2ff7b6ba21e18af2ecb93f2 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 18 Sep 2024 12:57:36 +0200 Subject: [PATCH 193/241] Refactor --- .../client-assertion-validation/package.json | 1 - .../client-assertion-validation/src/types.ts | 8 +++--- .../src/validation.ts | 7 +++-- .../client-assertion-validation/test/utils.ts | 10 ++++--- .../test/validation.test.ts | 26 ++++++++++--------- pnpm-lock.yaml | 3 --- 6 files changed, 28 insertions(+), 27 deletions(-) diff --git a/packages/client-assertion-validation/package.json b/packages/client-assertion-validation/package.json index 9386a1aa3d..d1a57c47f4 100644 --- a/packages/client-assertion-validation/package.json +++ b/packages/client-assertion-validation/package.json @@ -20,7 +20,6 @@ "license": "Apache-2.0", "dependencies": { "jsonwebtoken": "9.0.2", - "pagopa-interop-api-clients": "workspace:*", "pagopa-interop-commons-test": "workspace:*", "pagopa-interop-models": "workspace:*", "pagopa-interop-commons": "workspace:*", diff --git a/packages/client-assertion-validation/src/types.ts b/packages/client-assertion-validation/src/types.ts index 02a6b0e783..1047ee232f 100644 --- a/packages/client-assertion-validation/src/types.ts +++ b/packages/client-assertion-validation/src/types.ts @@ -1,10 +1,10 @@ -import { authorizationManagementApi } from "pagopa-interop-api-clients"; import { AgreementId, ApiError, ClientId, clientKindTokenStates, EServiceId, + ItemState, PurposeId, TenantId, } from "pagopa-interop-models"; @@ -68,11 +68,11 @@ export type Key = z.infer; export const ConsumerKey = Key.extend({ clientKind: z.literal(clientKindTokenStates.consumer), purposeId: PurposeId, // TODO is this naming ok? - purposeState: authorizationManagementApi.ClientComponentState, + purposeState: ItemState, agreementId: AgreementId, - agreementState: authorizationManagementApi.ClientComponentState, + agreementState: ItemState, eServiceId: EServiceId, // no field to map. Extract from GSIPK_eserviceId_descriptorId? - descriptorState: authorizationManagementApi.ClientComponentState, + descriptorState: ItemState, }).strict(); export type ConsumerKey = z.infer; diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index 474b60c921..86b53d4559 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -1,4 +1,3 @@ -import { authorizationServerApi } from "pagopa-interop-api-clients"; import { decode, JsonWebTokenError, @@ -82,14 +81,14 @@ export const validateClientAssertion = async ( }; export const validateRequestParameters = ( - request: authorizationServerApi.AccessTokenRequest -): ValidationResult => { + request: ClientAssertionValidationRequest +): ValidationResult => { const assertionTypeError = request.client_assertion_type !== EXPECTED_CLIENT_ASSERTION_TYPE ? invalidAssertionType(request.client_assertion_type) : undefined; - // TODO: this might be useless because authorizationServerApi.AccessTokenRequest has the string hard coded + // TODO: this might be useless because ClientAssertionValidationRequest has the string hard coded const grantTypeError = request.grant_type !== EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE ? invalidGrantType(request.grant_type) diff --git a/packages/client-assertion-validation/test/utils.ts b/packages/client-assertion-validation/test/utils.ts index 22e96fee17..833dcf48b0 100644 --- a/packages/client-assertion-validation/test/utils.ts +++ b/packages/client-assertion-validation/test/utils.ts @@ -1,5 +1,4 @@ import crypto from "crypto"; -import { authorizationServerApi } from "pagopa-interop-api-clients"; import * as jwt from "jsonwebtoken"; import { ClientId, @@ -9,7 +8,12 @@ import { PurposeId, TenantId, } from "pagopa-interop-models"; -import { ApiKey, ClientAssertionHeader, ConsumerKey } from ".././src/types.js"; +import { + ApiKey, + ClientAssertionHeader, + ClientAssertionValidationRequest, + ConsumerKey, +} from ".././src/types.js"; export const value64chars = crypto.randomBytes(32).toString("hex"); @@ -87,7 +91,7 @@ export const getMockApiKey = (): ApiKey => ({ }); export const getMockAccessTokenRequest = - (): authorizationServerApi.AccessTokenRequest => { + (): ClientAssertionValidationRequest => { const keySet = crypto.generateKeyPairSync("rsa", { modulusLength: 2048, }); diff --git a/packages/client-assertion-validation/test/validation.test.ts b/packages/client-assertion-validation/test/validation.test.ts index 93b3909525..68535454a6 100644 --- a/packages/client-assertion-validation/test/validation.test.ts +++ b/packages/client-assertion-validation/test/validation.test.ts @@ -25,7 +25,6 @@ import { inactiveAgreement, inactiveEService, inactivePurpose, - invalidAssertionType, invalidAudience, invalidAudienceFormat, invalidClientAssertionFormat, @@ -65,17 +64,20 @@ describe("validation test", () => { expect(errors).toBeUndefined(); }); - it("invalidAssertionType", () => { - const wrongAssertionType = "something-wrong"; - const request = { - ...getMockAccessTokenRequest(), - client_assertion_type: wrongAssertionType, - }; - const { errors } = validateRequestParameters(request); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(invalidAssertionType(wrongAssertionType)); - }); + // it("invalidAssertionType", () => { + // TODO how to test this if "something-wrong" can't be assigned to the property? + // possible solution: the property is a string (not literal) and the check is done later + + // const wrongAssertionType = "something-wrong"; + // const request = { + // ...getMockAccessTokenRequest(), + // client_assertion_type: wrongAssertionType, + // }; + // const { errors } = validateRequestParameters(request); + // expect(errors).toBeDefined(); + // expect(errors).toHaveLength(1); + // expect(errors![0]).toEqual(invalidAssertionType(wrongAssertionType)); + // }); // it("invalidGrantType", () => { // const wrongGrantType = "something-wrong"; // const request = { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 91c5e4a2d5..1ba5868bb3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1037,9 +1037,6 @@ importers: jsonwebtoken: specifier: 9.0.2 version: 9.0.2 - pagopa-interop-api-clients: - specifier: workspace:* - version: link:../api-clients pagopa-interop-commons: specifier: workspace:* version: link:../commons From d8a2dcc58ad5c0f5dd7bb8aed652a67589218e41 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 18 Sep 2024 16:00:40 +0200 Subject: [PATCH 194/241] Comment function --- packages/client-assertion-validation/src/validation.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index 86b53d4559..2ab35847c1 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -47,15 +47,20 @@ import { unexpectedKeyType, } from "./errors.js"; +/* +TEMPLATE for client assertion validation + export const validateClientAssertion = async ( request: ClientAssertionValidationRequest, - key: ConsumerKey | ApiKey // TODO use just Key? ): Promise> => { const { errors: parametersErrors } = validateRequestParameters(request); const { errors: clientAssertionVerificationErrors, data: jwt } = verifyClientAssertion(request.client_assertion, request.client_id); + // TO DO retrieve key + + const { errors: clientAssertionSignatureErrors } = verifyClientAssertionSignature(request.client_assertion, key); @@ -79,6 +84,7 @@ export const validateClientAssertion = async ( return successfulValidation(jwt); }; +*/ export const validateRequestParameters = ( request: ClientAssertionValidationRequest From 7724295f5542251adf08a05cf724d08957a458cd Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Fri, 20 Sep 2024 10:57:25 +0200 Subject: [PATCH 195/241] Fix GSIPK_agreementTimestamp typo --- .../src/token-generation-readmodel/platform-states-entry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/models/src/token-generation-readmodel/platform-states-entry.ts b/packages/models/src/token-generation-readmodel/platform-states-entry.ts index 52bc81ba1a..e0566428d8 100644 --- a/packages/models/src/token-generation-readmodel/platform-states-entry.ts +++ b/packages/models/src/token-generation-readmodel/platform-states-entry.ts @@ -51,7 +51,7 @@ export type PlatformStatesPurposeEntry = z.infer< export const PlatformStatesAgreementEntry = PlatformStatesBaseEntry.extend({ PK: PlatformStatesAgreementPK, GSIPK_consumerId_eserviceId: GSIPKConsumerIdEServiceId, - GSISK_agreementTimestamp: z.string().datetime(), + GSIPK_agreementTimestamp: z.string().datetime(), agreementDescriptorId: z.string(), }); export type PlatformStatesAgreementEntry = z.infer< From db0de4716880804b47fbc984ac0fa318b9bdbcbc Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Fri, 20 Sep 2024 15:33:03 +0200 Subject: [PATCH 196/241] Revert "Fix GSIPK_agreementTimestamp typo" This reverts commit 7724295f5542251adf08a05cf724d08957a458cd. --- .../src/token-generation-readmodel/platform-states-entry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/models/src/token-generation-readmodel/platform-states-entry.ts b/packages/models/src/token-generation-readmodel/platform-states-entry.ts index e0566428d8..52bc81ba1a 100644 --- a/packages/models/src/token-generation-readmodel/platform-states-entry.ts +++ b/packages/models/src/token-generation-readmodel/platform-states-entry.ts @@ -51,7 +51,7 @@ export type PlatformStatesPurposeEntry = z.infer< export const PlatformStatesAgreementEntry = PlatformStatesBaseEntry.extend({ PK: PlatformStatesAgreementPK, GSIPK_consumerId_eserviceId: GSIPKConsumerIdEServiceId, - GSIPK_agreementTimestamp: z.string().datetime(), + GSISK_agreementTimestamp: z.string().datetime(), agreementDescriptorId: z.string(), }); export type PlatformStatesAgreementEntry = z.infer< From ab92c540fc485356d31c0a7f575fc5fb063eeb1a Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 10:00:35 +0200 Subject: [PATCH 197/241] Remove unused line --- packages/catalog-platformstate-writer/test/utils.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/utils.ts b/packages/catalog-platformstate-writer/test/utils.ts index 1931401502..29cd2b1a49 100644 --- a/packages/catalog-platformstate-writer/test/utils.ts +++ b/packages/catalog-platformstate-writer/test/utils.ts @@ -5,5 +5,3 @@ export const config = inject("tokenGenerationReadModelConfig"); export const { cleanup } = setupTestContainersVitest(); afterEach(cleanup); - -// export const eservices = readModelRepository.eservices; From 2ed47eb3a4a9642cdab2622a48cd32a91e882500 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 10:01:01 +0200 Subject: [PATCH 198/241] Refactor --- packages/catalog-platformstate-writer/.env | 2 ++ packages/catalog-platformstate-writer/src/index.ts | 8 ++++++-- .../src/config/tokenGenerationReadmodelDbConfig.ts | 4 ++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/catalog-platformstate-writer/.env b/packages/catalog-platformstate-writer/.env index 223c09deae..be9f6abc9e 100644 --- a/packages/catalog-platformstate-writer/.env +++ b/packages/catalog-platformstate-writer/.env @@ -12,3 +12,5 @@ TOKEN_GENERATION_READMODEL_TABLE_NAME_PLATFORM="platform-states" TOKEN_GENERATION_READMODEL_TABLE_NAME_TOKEN_GENERATION="token-generation-states" AWS_REGION="eu-central-1" +AWS_ACCESS_KEY_ID="key" +AWS_SECRET_ACCESS_KEY="secret" diff --git a/packages/catalog-platformstate-writer/src/index.ts b/packages/catalog-platformstate-writer/src/index.ts index 642c3508d3..a9875fa099 100644 --- a/packages/catalog-platformstate-writer/src/index.ts +++ b/packages/catalog-platformstate-writer/src/index.ts @@ -9,10 +9,14 @@ import { handleMessageV2 } from "./consumerServiceV2.js"; import { config } from "./config/config.js"; const dynamoDBClient = new DynamoDBClient({ - credentials: { accessKeyId: "key", secretAccessKey: "secret" }, - region: "eu-central-1", + credentials: { + accessKeyId: config.awsAccessKeyId, + secretAccessKey: config.awsSecretAccessKey, + }, + region: config.awsRegion, endpoint: `http://${config.tokenGenerationReadModelDbHost}:${config.tokenGenerationReadModelDbPort}`, }); + async function processMessage({ message, partition, diff --git a/packages/commons/src/config/tokenGenerationReadmodelDbConfig.ts b/packages/commons/src/config/tokenGenerationReadmodelDbConfig.ts index 6ad8699555..7c30841aef 100644 --- a/packages/commons/src/config/tokenGenerationReadmodelDbConfig.ts +++ b/packages/commons/src/config/tokenGenerationReadmodelDbConfig.ts @@ -6,6 +6,8 @@ export const TokenGenerationReadModelDbConfig = z TOKEN_GENERATION_READMODEL_PORT: z.coerce.number().min(1001), TOKEN_GENERATION_READMODEL_TABLE_NAME_PLATFORM: z.string(), TOKEN_GENERATION_READMODEL_TABLE_NAME_TOKEN_GENERATION: z.string(), + AWS_ACCESS_KEY_ID: z.string(), + AWS_SECRET_ACCESS_KEY: z.string(), }) .transform((c) => ({ tokenGenerationReadModelDbHost: c.TOKEN_GENERATION_READMODEL_HOST, @@ -14,6 +16,8 @@ export const TokenGenerationReadModelDbConfig = z c.TOKEN_GENERATION_READMODEL_TABLE_NAME_PLATFORM, tokenGenerationReadModelTableNameTokenGeneration: c.TOKEN_GENERATION_READMODEL_TABLE_NAME_TOKEN_GENERATION, + awsAccessKeyId: c.AWS_ACCESS_KEY_ID, + awsSecretAccessKey: c.AWS_SECRET_ACCESS_KEY, })); export type TokenGenerationReadModelDbConfig = z.infer< From 186061614bb75f45597288213cc0d1c838cbace1 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 10:22:46 +0200 Subject: [PATCH 199/241] Refactor tables setup --- ...logPlatformstateWriter.integration.test.ts | 72 +---- packages/commons-test/package.json | 3 +- packages/commons-test/src/index.ts | 1 + .../commons-test/src/setupDynamoDBtables.ts | 74 +++++ pnpm-lock.yaml | 301 ++++++------------ 5 files changed, 174 insertions(+), 277 deletions(-) create mode 100644 packages/commons-test/src/setupDynamoDBtables.ts diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 5fda2329ab..e34eef11c1 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -30,10 +30,6 @@ import { } from "pagopa-interop-models"; import { ConditionalCheckFailedException, - CreateTableCommand, - CreateTableInput, - DeleteTableCommand, - DeleteTableInput, DynamoDBClient, } from "@aws-sdk/client-dynamodb"; import { @@ -41,6 +37,8 @@ import { getMockEService, getMockDocument, getMockTokenStatesClientPurposeEntry, + buildDynamoDBTables, + deleteDynamoDBTables, } from "pagopa-interop-commons-test"; import { deleteCatalogEntry, @@ -73,72 +71,10 @@ describe("integration tests", async () => { }`, }); beforeEach(async () => { - if (!config) { - // to do: why is this needed? - fail(); - } - const platformTableDefinition: CreateTableInput = { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - TableName: config.tokenGenerationReadModelTableNamePlatform, - AttributeDefinitions: [{ AttributeName: "PK", AttributeType: "S" }], - KeySchema: [{ AttributeName: "PK", KeyType: "HASH" }], - BillingMode: "PAY_PER_REQUEST", - }; - const command1 = new CreateTableCommand(platformTableDefinition); - await dynamoDBClient.send(command1); - - const tokenGenerationTableDefinition: CreateTableInput = { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - TableName: config.tokenGenerationReadModelTableNameTokenGeneration, - AttributeDefinitions: [ - { AttributeName: "PK", AttributeType: "S" }, - { AttributeName: "GSIPK_eserviceId_descriptorId", AttributeType: "S" }, - ], - KeySchema: [{ AttributeName: "PK", KeyType: "HASH" }], - BillingMode: "PAY_PER_REQUEST", - GlobalSecondaryIndexes: [ - { - IndexName: "GSIPK_eserviceId_descriptorId", - KeySchema: [ - { - AttributeName: "GSIPK_eserviceId_descriptorId", - KeyType: "HASH", - }, - ], - Projection: { - NonKeyAttributes: [], - ProjectionType: "ALL", - }, - // ProvisionedThroughput: { - // ReadCapacityUnits: 5, - // WriteCapacityUnits: 5, - // }, - }, - ], - }; - const command2 = new CreateTableCommand(tokenGenerationTableDefinition); - await dynamoDBClient.send(command2); - // console.log(result); - - // const tablesResult = await dynamoDBClient.listTables(); - // console.log(tablesResult.TableNames); + await buildDynamoDBTables(dynamoDBClient); }); afterEach(async () => { - if (!config) { - fail(); - } - const tableToDelete1: DeleteTableInput = { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - TableName: config.tokenGenerationReadModelTableNamePlatform, - }; - const tableToDelete2: DeleteTableInput = { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - TableName: config.tokenGenerationReadModelTableNameTokenGeneration, - }; - const command1 = new DeleteTableCommand(tableToDelete1); - await dynamoDBClient.send(command1); - const command2 = new DeleteTableCommand(tableToDelete2); - await dynamoDBClient.send(command2); + await deleteDynamoDBTables(dynamoDBClient); }); const mockDate = new Date(); beforeAll(() => { diff --git a/packages/commons-test/package.json b/packages/commons-test/package.json index e90be1af24..008c17bceb 100644 --- a/packages/commons-test/package.json +++ b/packages/commons-test/package.json @@ -19,14 +19,15 @@ "license": "Apache-2.0", "devDependencies": { "@anatine/zod-mock": "3.13.4", + "@aws-sdk/client-dynamodb": "3.637.0", "@pagopa/eslint-config": "3.0.0", "@protobuf-ts/runtime": "2.9.4", "@testcontainers/postgresql": "10.9.0", "@types/jsonwebtoken": "9.0.6", "@types/uuid": "9.0.8", "@zodios/core": "10.9.6", - "dotenv-flow": "4.1.0", "axios": "1.7.2", + "dotenv-flow": "4.1.0", "jsonwebtoken": "9.0.2", "pagopa-interop-commons": "workspace:*", "pagopa-interop-models": "workspace:*", diff --git a/packages/commons-test/src/index.ts b/packages/commons-test/src/index.ts index 449587b1f8..1ad03def43 100644 --- a/packages/commons-test/src/index.ts +++ b/packages/commons-test/src/index.ts @@ -6,3 +6,4 @@ export * from "./riskAnalysisTestUtils.js"; export * from "./setupTestContainersVitest.js"; export * from "./setupTestContainersVitestGlobal.js"; export * from "./protobufConvertersToV1/catalogProtobufConverterToV1.js"; +export * from "./setupDynamoDBtables.js"; diff --git a/packages/commons-test/src/setupDynamoDBtables.ts b/packages/commons-test/src/setupDynamoDBtables.ts new file mode 100644 index 0000000000..a8ed5e8291 --- /dev/null +++ b/packages/commons-test/src/setupDynamoDBtables.ts @@ -0,0 +1,74 @@ +import { + CreateTableCommand, + CreateTableInput, + DeleteTableCommand, + DeleteTableInput, + DynamoDBClient, +} from "@aws-sdk/client-dynamodb"; + +export const buildDynamoDBTables = async ( + dynamoDBClient: DynamoDBClient +): Promise => { + const platformTableDefinition: CreateTableInput = { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + TableName: "platform-states", + AttributeDefinitions: [{ AttributeName: "PK", AttributeType: "S" }], + KeySchema: [{ AttributeName: "PK", KeyType: "HASH" }], + BillingMode: "PAY_PER_REQUEST", + }; + const command1 = new CreateTableCommand(platformTableDefinition); + await dynamoDBClient.send(command1); + + const tokenGenerationTableDefinition: CreateTableInput = { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + TableName: "token-generation-states", + AttributeDefinitions: [ + { AttributeName: "PK", AttributeType: "S" }, + { AttributeName: "GSIPK_eserviceId_descriptorId", AttributeType: "S" }, + ], + KeySchema: [{ AttributeName: "PK", KeyType: "HASH" }], + BillingMode: "PAY_PER_REQUEST", + GlobalSecondaryIndexes: [ + { + IndexName: "GSIPK_eserviceId_descriptorId", + KeySchema: [ + { + AttributeName: "GSIPK_eserviceId_descriptorId", + KeyType: "HASH", + }, + ], + Projection: { + NonKeyAttributes: [], + ProjectionType: "ALL", + }, + // ProvisionedThroughput: { + // ReadCapacityUnits: 5, + // WriteCapacityUnits: 5, + // }, + }, + ], + }; + const command2 = new CreateTableCommand(tokenGenerationTableDefinition); + await dynamoDBClient.send(command2); + // console.log(result); + + // const tablesResult = await dynamoDBClient.listTables(); + // console.log(tablesResult.TableNames); +}; + +export const deleteDynamoDBTables = async ( + dynamoDBClient: DynamoDBClient +): Promise => { + const tableToDelete1: DeleteTableInput = { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + TableName: "platform-states", + }; + const tableToDelete2: DeleteTableInput = { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + TableName: "token-generation-states", + }; + const command1 = new DeleteTableCommand(tableToDelete1); + await dynamoDBClient.send(command1); + const command2 = new DeleteTableCommand(tableToDelete2); + await dynamoDBClient.send(command2); +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45285d2c77..07bdcf68e6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1192,6 +1192,9 @@ importers: '@anatine/zod-mock': specifier: 3.13.4 version: 3.13.4(@faker-js/faker@8.4.1)(zod@3.23.8) + '@aws-sdk/client-dynamodb': + specifier: 3.637.0 + version: 3.637.0 '@pagopa/eslint-config': specifier: 3.0.0 version: 3.0.0(typescript@5.4.5) @@ -2105,7 +2108,6 @@ packages: '@aws-sdk/util-locate-window': 3.568.0 '@smithy/util-utf8': 2.3.0 tslib: 2.6.3 - dev: false /@aws-crypto/sha256-js@4.0.0: resolution: {integrity: sha512-MHGJyjE7TX9aaqXj7zk2ppnFUOhaDs5sP+HtNS0evOxn72c+5njUmyJmpGd7TfyoDznZlHMmdo/xGUdu2NIjNQ==} @@ -2122,13 +2124,11 @@ packages: '@aws-crypto/util': 5.2.0 '@aws-sdk/types': 3.609.0 tslib: 2.6.3 - dev: false /@aws-crypto/supports-web-crypto@5.2.0: resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} dependencies: tslib: 2.6.3 - dev: false /@aws-crypto/util@4.0.0: resolution: {integrity: sha512-2EnmPy2gsFZ6m8bwUQN4jq+IyXV3quHAcwPOS6ZA3k+geujiqI8aRokO2kFJe+idJ/P3v4qWI186rVMo0+zLDQ==} @@ -2144,7 +2144,6 @@ packages: '@aws-sdk/types': 3.609.0 '@smithy/util-utf8': 2.3.0 tslib: 2.6.3 - dev: false /@aws-sdk/client-cognito-identity@3.609.0: resolution: {integrity: sha512-3kDTpia1iN/accayoH3MbZRbDvX2tzrKrBTU7wNNoazVrh+gOMS8KCOWrOB72F0V299l4FsfQhnl9BDMVrc1iw==} @@ -2165,28 +2164,28 @@ packages: '@aws-sdk/util-endpoints': 3.609.0 '@aws-sdk/util-user-agent-browser': 3.609.0 '@aws-sdk/util-user-agent-node': 3.609.0 - '@smithy/config-resolver': 3.0.4 - '@smithy/core': 2.2.4 - '@smithy/fetch-http-handler': 3.2.0 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 '@smithy/hash-node': 3.0.3 '@smithy/invalid-dependency': 3.0.3 - '@smithy/middleware-content-length': 3.0.3 - '@smithy/middleware-endpoint': 3.0.4 - '@smithy/middleware-retry': 3.0.7 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 '@smithy/middleware-serde': 3.0.3 '@smithy/middleware-stack': 3.0.3 - '@smithy/node-config-provider': 3.1.3 - '@smithy/node-http-handler': 3.1.1 - '@smithy/protocol-http': 4.0.3 - '@smithy/smithy-client': 3.1.5 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 '@smithy/types': 3.3.0 '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.7 - '@smithy/util-defaults-mode-node': 3.0.7 - '@smithy/util-endpoints': 2.0.4 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 '@smithy/util-middleware': 3.0.3 '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 @@ -2245,7 +2244,6 @@ packages: uuid: 9.0.1 transitivePeerDependencies: - aws-crt - dev: false /@aws-sdk/client-kms@3.600.0: resolution: {integrity: sha512-m1o8aiVrVjExw6O+8JszXV3hr8sCyXKOLq1WCwWJqYF6Uf4vCf8iTYISQB3skbKUnBJm4SxVA82iViGAtWB7JA==} @@ -2482,35 +2480,34 @@ packages: '@aws-sdk/util-endpoints': 3.609.0 '@aws-sdk/util-user-agent-browser': 3.609.0 '@aws-sdk/util-user-agent-node': 3.609.0 - '@smithy/config-resolver': 3.0.4 - '@smithy/core': 2.2.4 - '@smithy/fetch-http-handler': 3.2.0 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 '@smithy/hash-node': 3.0.3 '@smithy/invalid-dependency': 3.0.3 - '@smithy/middleware-content-length': 3.0.3 - '@smithy/middleware-endpoint': 3.0.4 - '@smithy/middleware-retry': 3.0.7 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 '@smithy/middleware-serde': 3.0.3 '@smithy/middleware-stack': 3.0.3 - '@smithy/node-config-provider': 3.1.3 - '@smithy/node-http-handler': 3.1.1 - '@smithy/protocol-http': 4.0.3 - '@smithy/smithy-client': 3.1.5 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 '@smithy/types': 3.3.0 '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.7 - '@smithy/util-defaults-mode-node': 3.0.7 - '@smithy/util-endpoints': 2.0.4 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 '@smithy/util-middleware': 3.0.3 '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: - aws-crt - dev: false /@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.609.0): resolution: {integrity: sha512-27bHALN6Qb6m6KZmPvRieJ/QRlj1lyac/GT2Rn5kJpre8Mpp+yxrtvp3h9PjNBty4lCeFEENfY4dGNSozBuBcw==} @@ -2560,7 +2557,6 @@ packages: tslib: 2.6.3 transitivePeerDependencies: - aws-crt - dev: false /@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0): resolution: {integrity: sha512-27bHALN6Qb6m6KZmPvRieJ/QRlj1lyac/GT2Rn5kJpre8Mpp+yxrtvp3h9PjNBty4lCeFEENfY4dGNSozBuBcw==} @@ -2610,7 +2606,6 @@ packages: tslib: 2.6.3 transitivePeerDependencies: - aws-crt - dev: false /@aws-sdk/client-sso@3.598.0: resolution: {integrity: sha512-nOI5lqPYa+YZlrrzwAJywJSw3MKVjvu6Ge2fCqQUNYMfxFB0NAaDFnl0EPjXi+sEbtCuz/uWE77poHbqiZ+7Iw==} @@ -2628,28 +2623,28 @@ packages: '@aws-sdk/util-endpoints': 3.598.0 '@aws-sdk/util-user-agent-browser': 3.598.0 '@aws-sdk/util-user-agent-node': 3.598.0 - '@smithy/config-resolver': 3.0.4 - '@smithy/core': 2.2.4 - '@smithy/fetch-http-handler': 3.2.0 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 '@smithy/hash-node': 3.0.3 '@smithy/invalid-dependency': 3.0.3 - '@smithy/middleware-content-length': 3.0.3 - '@smithy/middleware-endpoint': 3.0.4 - '@smithy/middleware-retry': 3.0.7 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 '@smithy/middleware-serde': 3.0.3 '@smithy/middleware-stack': 3.0.3 - '@smithy/node-config-provider': 3.1.3 - '@smithy/node-http-handler': 3.1.1 - '@smithy/protocol-http': 4.0.3 - '@smithy/smithy-client': 3.1.5 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 '@smithy/types': 3.3.0 '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.7 - '@smithy/util-defaults-mode-node': 3.0.7 - '@smithy/util-endpoints': 2.0.4 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 '@smithy/util-middleware': 3.0.3 '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 @@ -2674,35 +2669,34 @@ packages: '@aws-sdk/util-endpoints': 3.609.0 '@aws-sdk/util-user-agent-browser': 3.609.0 '@aws-sdk/util-user-agent-node': 3.609.0 - '@smithy/config-resolver': 3.0.4 - '@smithy/core': 2.2.4 - '@smithy/fetch-http-handler': 3.2.0 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 '@smithy/hash-node': 3.0.3 '@smithy/invalid-dependency': 3.0.3 - '@smithy/middleware-content-length': 3.0.3 - '@smithy/middleware-endpoint': 3.0.4 - '@smithy/middleware-retry': 3.0.7 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 '@smithy/middleware-serde': 3.0.3 '@smithy/middleware-stack': 3.0.3 - '@smithy/node-config-provider': 3.1.3 - '@smithy/node-http-handler': 3.1.1 - '@smithy/protocol-http': 4.0.3 - '@smithy/smithy-client': 3.1.5 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 '@smithy/types': 3.3.0 '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.7 - '@smithy/util-defaults-mode-node': 3.0.7 - '@smithy/util-endpoints': 2.0.4 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 '@smithy/util-middleware': 3.0.3 '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: - aws-crt - dev: false /@aws-sdk/client-sso@3.637.0: resolution: {integrity: sha512-+KjLvgX5yJYROWo3TQuwBJlHCY0zz9PsLuEolmXQn0BVK1L/m9GteZHtd+rEdAoDGBpE0Xqjy1oz5+SmtsaRUw==} @@ -2748,7 +2742,6 @@ packages: tslib: 2.6.3 transitivePeerDependencies: - aws-crt - dev: false /@aws-sdk/client-sts@3.600.0: resolution: {integrity: sha512-KQG97B7LvTtTiGmjlrG1LRAY8wUvCQzrmZVV5bjrJ/1oXAU7DITYwVbSJeX9NWg6hDuSk0VE3MFwIXS2SvfLIA==} @@ -2844,7 +2837,6 @@ packages: tslib: 2.6.3 transitivePeerDependencies: - aws-crt - dev: false /@aws-sdk/client-sts@3.637.0: resolution: {integrity: sha512-xUi7x4qDubtA8QREtlblPuAcn91GS/09YVEY/RwU7xCY0aqGuFwgszAANlha4OUIqva8oVj2WO4gJuG+iaSnhw==} @@ -2892,7 +2884,6 @@ packages: tslib: 2.6.3 transitivePeerDependencies: - aws-crt - dev: false /@aws-sdk/core@3.598.0: resolution: {integrity: sha512-HaSjt7puO5Cc7cOlrXFCW0rtA0BM9lvzjl56x0A20Pt+0wxXGeTOZZOkXQIepbrFkV2e/HYukuT9e99vXDm59g==} @@ -2911,14 +2902,13 @@ packages: resolution: {integrity: sha512-ptqw+DTxLr01+pKjDUuo53SEDzI+7nFM3WfQaEo0yhDg8vWw8PER4sWj1Ysx67ksctnZesPUjqxd5SHbtdBxiA==} engines: {node: '>=16.0.0'} dependencies: - '@smithy/core': 2.2.4 - '@smithy/protocol-http': 4.0.3 + '@smithy/core': 2.4.0 + '@smithy/protocol-http': 4.1.0 '@smithy/signature-v4': 3.1.2 - '@smithy/smithy-client': 3.1.5 + '@smithy/smithy-client': 3.2.0 '@smithy/types': 3.3.0 fast-xml-parser: 4.2.5 tslib: 2.6.3 - dev: false /@aws-sdk/core@3.635.0: resolution: {integrity: sha512-i1x/E/sgA+liUE1XJ7rj1dhyXpAKO1UKFUcTTHXok2ARjWTvszHnSXMOsB77aPbmn0fUp1JTx2kHUAZ1LVt5Bg==} @@ -2934,7 +2924,6 @@ packages: '@smithy/util-middleware': 3.0.3 fast-xml-parser: 4.4.1 tslib: 2.6.3 - dev: false /@aws-sdk/credential-provider-cognito-identity@3.609.0: resolution: {integrity: sha512-BqrpAXRr64dQ/uZsRB2wViGKTkVRlfp8Q+Zd7Bc8Ikk+YXjPtl+IyWXKtdKQ3LBO255KwAcPmra5oFC+2R1GOQ==} @@ -2967,7 +2956,6 @@ packages: '@smithy/property-provider': 3.1.3 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@aws-sdk/credential-provider-env@3.620.1: resolution: {integrity: sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg==} @@ -2977,20 +2965,19 @@ packages: '@smithy/property-provider': 3.1.3 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@aws-sdk/credential-provider-http@3.598.0: resolution: {integrity: sha512-N7cIafi4HVlQvEgvZSo1G4T9qb/JMLGMdBsDCT5XkeJrF0aptQWzTFH0jIdZcLrMYvzPcuEyO3yCBe6cy/ba0g==} engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.598.0 - '@smithy/fetch-http-handler': 3.2.0 - '@smithy/node-http-handler': 3.1.1 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/node-http-handler': 3.1.4 '@smithy/property-provider': 3.1.3 - '@smithy/protocol-http': 4.0.3 - '@smithy/smithy-client': 3.1.5 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 '@smithy/types': 3.3.0 - '@smithy/util-stream': 3.0.5 + '@smithy/util-stream': 3.1.3 tslib: 2.6.3 dev: false @@ -2999,15 +2986,14 @@ packages: engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/fetch-http-handler': 3.2.0 - '@smithy/node-http-handler': 3.1.1 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/node-http-handler': 3.1.4 '@smithy/property-provider': 3.1.3 - '@smithy/protocol-http': 4.0.3 - '@smithy/smithy-client': 3.1.5 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 '@smithy/types': 3.3.0 - '@smithy/util-stream': 3.0.5 + '@smithy/util-stream': 3.1.3 tslib: 2.6.3 - dev: false /@aws-sdk/credential-provider-http@3.635.0: resolution: {integrity: sha512-iJyRgEjOCQlBMXqtwPLIKYc7Bsc6nqjrZybdMDenPDa+kmLg7xh8LxHsu9088e+2/wtLicE34FsJJIfzu3L82g==} @@ -3022,7 +3008,6 @@ packages: '@smithy/types': 3.3.0 '@smithy/util-stream': 3.1.3 tslib: 2.6.3 - dev: false /@aws-sdk/credential-provider-ini@3.598.0(@aws-sdk/client-sso-oidc@3.600.0)(@aws-sdk/client-sts@3.600.0): resolution: {integrity: sha512-/ppcIVUbRwDIwJDoYfp90X3+AuJo2mvE52Y1t2VSrvUovYn6N4v95/vXj6LS8CNDhz2jvEJYmu+0cTMHdhI6eA==} @@ -3037,9 +3022,9 @@ packages: '@aws-sdk/credential-provider-sso': 3.598.0(@aws-sdk/client-sso-oidc@3.600.0) '@aws-sdk/credential-provider-web-identity': 3.598.0(@aws-sdk/client-sts@3.600.0) '@aws-sdk/types': 3.598.0 - '@smithy/credential-provider-imds': 3.1.3 + '@smithy/credential-provider-imds': 3.2.0 '@smithy/property-provider': 3.1.3 - '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: @@ -3060,15 +3045,14 @@ packages: '@aws-sdk/credential-provider-sso': 3.609.0(@aws-sdk/client-sso-oidc@3.609.0) '@aws-sdk/credential-provider-web-identity': 3.609.0(@aws-sdk/client-sts@3.609.0) '@aws-sdk/types': 3.609.0 - '@smithy/credential-provider-imds': 3.1.3 + '@smithy/credential-provider-imds': 3.2.0 '@smithy/property-provider': 3.1.3 - '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt - dev: false /@aws-sdk/credential-provider-ini@3.609.0(@aws-sdk/client-sso-oidc@3.637.0)(@aws-sdk/client-sts@3.609.0): resolution: {integrity: sha512-hwaBfXuBTv6/eAdEsDfGcteYUW6Km7lvvubbxEdxIuJNF3vswR7RMGIXaEC37hhPkTTgd3H0TONammhwZIfkog==} @@ -3083,9 +3067,9 @@ packages: '@aws-sdk/credential-provider-sso': 3.609.0(@aws-sdk/client-sso-oidc@3.637.0) '@aws-sdk/credential-provider-web-identity': 3.609.0(@aws-sdk/client-sts@3.609.0) '@aws-sdk/types': 3.609.0 - '@smithy/credential-provider-imds': 3.1.3 + '@smithy/credential-provider-imds': 3.2.0 '@smithy/property-provider': 3.1.3 - '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: @@ -3114,7 +3098,6 @@ packages: transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt - dev: false /@aws-sdk/credential-provider-ini@3.637.0(@aws-sdk/client-sso-oidc@3.637.0)(@aws-sdk/client-sts@3.637.0): resolution: {integrity: sha512-h+PFCWfZ0Q3Dx84SppET/TFpcQHmxFW8/oV9ArEvMilw4EBN+IlxgbL0CnHwjHW64szcmrM0mbebjEfHf4FXmw==} @@ -3137,7 +3120,6 @@ packages: transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt - dev: false /@aws-sdk/credential-provider-node@3.600.0(@aws-sdk/client-sso-oidc@3.600.0)(@aws-sdk/client-sts@3.600.0): resolution: {integrity: sha512-1pC7MPMYD45J7yFjA90SxpR0yaSvy+yZiq23aXhAPZLYgJBAxHLu0s0mDCk/piWGPh8+UGur5K0bVdx4B1D5hw==} @@ -3172,16 +3154,15 @@ packages: '@aws-sdk/credential-provider-sso': 3.609.0(@aws-sdk/client-sso-oidc@3.609.0) '@aws-sdk/credential-provider-web-identity': 3.609.0(@aws-sdk/client-sts@3.609.0) '@aws-sdk/types': 3.609.0 - '@smithy/credential-provider-imds': 3.1.3 + '@smithy/credential-provider-imds': 3.2.0 '@smithy/property-provider': 3.1.3 - '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - '@aws-sdk/client-sts' - aws-crt - dev: false /@aws-sdk/credential-provider-node@3.609.0(@aws-sdk/client-sso-oidc@3.637.0)(@aws-sdk/client-sts@3.609.0): resolution: {integrity: sha512-4J8/JRuqfxJDGD9jTHVCBxCvYt7/Vgj2Stlhj930mrjFPO/yRw8ilAAZxBWe0JHPX3QwepCmh4ErZe53F5ysxQ==} @@ -3194,9 +3175,9 @@ packages: '@aws-sdk/credential-provider-sso': 3.609.0(@aws-sdk/client-sso-oidc@3.637.0) '@aws-sdk/credential-provider-web-identity': 3.609.0(@aws-sdk/client-sts@3.609.0) '@aws-sdk/types': 3.609.0 - '@smithy/credential-provider-imds': 3.1.3 + '@smithy/credential-provider-imds': 3.2.0 '@smithy/property-provider': 3.1.3 - '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: @@ -3225,7 +3206,6 @@ packages: - '@aws-sdk/client-sso-oidc' - '@aws-sdk/client-sts' - aws-crt - dev: false /@aws-sdk/credential-provider-node@3.637.0(@aws-sdk/client-sso-oidc@3.637.0)(@aws-sdk/client-sts@3.637.0): resolution: {integrity: sha512-yoEhoxJJfs7sPVQ6Is939BDQJZpZCoUgKr/ySse4YKOZ24t4VqgHA6+wV7rYh+7IW24Rd91UTvEzSuHYTlxlNA==} @@ -3247,7 +3227,6 @@ packages: - '@aws-sdk/client-sso-oidc' - '@aws-sdk/client-sts' - aws-crt - dev: false /@aws-sdk/credential-provider-process@3.598.0: resolution: {integrity: sha512-rM707XbLW8huMk722AgjVyxu2tMZee++fNA8TJVNgs1Ma02Wx6bBrfIvlyK0rCcIRb0WdQYP6fe3Xhiu4e8IBA==} @@ -3255,7 +3234,7 @@ packages: dependencies: '@aws-sdk/types': 3.598.0 '@smithy/property-provider': 3.1.3 - '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 dev: false @@ -3266,10 +3245,9 @@ packages: dependencies: '@aws-sdk/types': 3.609.0 '@smithy/property-provider': 3.1.3 - '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@aws-sdk/credential-provider-process@3.620.1: resolution: {integrity: sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==} @@ -3280,7 +3258,6 @@ packages: '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@aws-sdk/credential-provider-sso@3.598.0(@aws-sdk/client-sso-oidc@3.600.0): resolution: {integrity: sha512-5InwUmrAuqQdOOgxTccRayMMkSmekdLk6s+az9tmikq0QFAHUCtofI+/fllMXSR9iL6JbGYi1940+EUmS4pHJA==} @@ -3290,7 +3267,7 @@ packages: '@aws-sdk/token-providers': 3.598.0(@aws-sdk/client-sso-oidc@3.600.0) '@aws-sdk/types': 3.598.0 '@smithy/property-provider': 3.1.3 - '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: @@ -3306,13 +3283,12 @@ packages: '@aws-sdk/token-providers': 3.609.0(@aws-sdk/client-sso-oidc@3.609.0) '@aws-sdk/types': 3.609.0 '@smithy/property-provider': 3.1.3 - '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt - dev: false /@aws-sdk/credential-provider-sso@3.609.0(@aws-sdk/client-sso-oidc@3.637.0): resolution: {integrity: sha512-oQPGDKMMIxjvTcm86g07RPYeC7mCNk+29dPpY15ZAPRpAF7F0tircsC3wT9fHzNaKShEyK5LuI5Kg/uxsdy+Iw==} @@ -3322,7 +3298,7 @@ packages: '@aws-sdk/token-providers': 3.609.0(@aws-sdk/client-sso-oidc@3.637.0) '@aws-sdk/types': 3.609.0 '@smithy/property-provider': 3.1.3 - '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: @@ -3344,7 +3320,6 @@ packages: transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt - dev: false /@aws-sdk/credential-provider-web-identity@3.598.0(@aws-sdk/client-sts@3.600.0): resolution: {integrity: sha512-GV5GdiMbz5Tz9JO4NJtRoFXjW0GPEujA0j+5J/B723rTN+REHthJu48HdBKouHGhdzkDWkkh1bu52V02Wprw8w==} @@ -3370,7 +3345,6 @@ packages: '@smithy/property-provider': 3.1.3 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@aws-sdk/credential-provider-web-identity@3.621.0(@aws-sdk/client-sts@3.609.0): resolution: {integrity: sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==} @@ -3383,7 +3357,6 @@ packages: '@smithy/property-provider': 3.1.3 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@aws-sdk/credential-provider-web-identity@3.621.0(@aws-sdk/client-sts@3.637.0): resolution: {integrity: sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==} @@ -3396,7 +3369,6 @@ packages: '@smithy/property-provider': 3.1.3 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@aws-sdk/credential-providers@3.609.0(@aws-sdk/client-sso-oidc@3.637.0): resolution: {integrity: sha512-bJKMY4QwRVderh8R2s9kukoZhuNZew/xzwPa9DRRFVOIsznsS0faAdmAAFrKb8e06YyQq6DiZP0BfFyVHAXE2A==} @@ -3429,7 +3401,6 @@ packages: dependencies: mnemonist: 0.38.3 tslib: 2.6.3 - dev: false /@aws-sdk/middleware-bucket-endpoint@3.598.0: resolution: {integrity: sha512-PM7BcFfGUSkmkT6+LU9TyJiB4S8yI7dfuKQDwK5ZR3P7MKaK4Uj4yyDiv0oe5xvkF6+O2+rShj+eh8YuWkOZ/Q==} @@ -3454,7 +3425,6 @@ packages: '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@aws-sdk/middleware-expect-continue@3.598.0: resolution: {integrity: sha512-ZuHW18kaeHR8TQyhEOYMr8VwiIh0bMvF7J1OTqXHxDteQIavJWA3CbfZ9sgS4XGtrBZDyHJhjZKeCfLhN2rq3w==} @@ -3495,10 +3465,9 @@ packages: engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/protocol-http': 4.0.3 + '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@aws-sdk/middleware-host-header@3.620.0: resolution: {integrity: sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg==} @@ -3508,7 +3477,6 @@ packages: '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@aws-sdk/middleware-location-constraint@3.598.0: resolution: {integrity: sha512-8oybQxN3F1ISOMULk7JKJz5DuAm5hCUcxMW9noWShbxTJuStNvuHf/WLUzXrf8oSITyYzIHPtf8VPlKR7I3orQ==} @@ -3535,7 +3503,6 @@ packages: '@aws-sdk/types': 3.609.0 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@aws-sdk/middleware-recursion-detection@3.598.0: resolution: {integrity: sha512-vjT9BeFY9FeN0f8hm2l6F53tI0N5bUq6RcDkQXKNabXBnQxKptJRad6oP2X5y3FoVfBLOuDkQgiC2940GIPxtQ==} @@ -3552,10 +3519,9 @@ packages: engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/protocol-http': 4.0.3 + '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@aws-sdk/middleware-recursion-detection@3.620.0: resolution: {integrity: sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==} @@ -3565,7 +3531,6 @@ packages: '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@aws-sdk/middleware-sdk-s3@3.598.0: resolution: {integrity: sha512-5AGtLAh9wyK6ANPYfaKTqJY1IFJyePIxsEbxa7zS6REheAqyVmgJFaGu3oQ5XlxfGr5Uq59tFTRkyx26G1HkHA==} @@ -3633,10 +3598,9 @@ packages: dependencies: '@aws-sdk/types': 3.609.0 '@aws-sdk/util-endpoints': 3.609.0 - '@smithy/protocol-http': 4.0.3 + '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@aws-sdk/middleware-user-agent@3.637.0: resolution: {integrity: sha512-EYo0NE9/da/OY8STDsK2LvM4kNa79DBsf4YVtaG4P5pZ615IeFsD8xOHZeuJmUrSMlVQ8ywPRX7WMucUybsKug==} @@ -3647,7 +3611,6 @@ packages: '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@aws-sdk/region-config-resolver@3.598.0: resolution: {integrity: sha512-oYXhmTokSav4ytmWleCr3rs/1nyvZW/S0tdi6X7u+dLNL5Jee+uMxWGzgOrWK6wrQOzucLVjS4E/wA11Kv2GTw==} @@ -3666,12 +3629,11 @@ packages: engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/node-config-provider': 3.1.3 + '@smithy/node-config-provider': 3.1.4 '@smithy/types': 3.3.0 '@smithy/util-config-provider': 3.0.0 '@smithy/util-middleware': 3.0.3 tslib: 2.6.3 - dev: false /@aws-sdk/region-config-resolver@3.614.0: resolution: {integrity: sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==} @@ -3683,7 +3645,6 @@ packages: '@smithy/util-config-provider': 3.0.0 '@smithy/util-middleware': 3.0.3 tslib: 2.6.3 - dev: false /@aws-sdk/signature-v4-multi-region@3.598.0: resolution: {integrity: sha512-1r/EyTrO1gSa1FirnR8V7mabr7gk+l+HkyTI0fcTSr8ucB7gmYyW6WjkY8JCz13VYHFK62usCEDS7yoJoJOzTA==} @@ -3706,7 +3667,7 @@ packages: '@aws-sdk/client-sso-oidc': 3.600.0(@aws-sdk/client-sts@3.600.0) '@aws-sdk/types': 3.598.0 '@smithy/property-provider': 3.1.3 - '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 dev: false @@ -3720,10 +3681,9 @@ packages: '@aws-sdk/client-sso-oidc': 3.609.0(@aws-sdk/client-sts@3.609.0) '@aws-sdk/types': 3.609.0 '@smithy/property-provider': 3.1.3 - '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@aws-sdk/token-providers@3.609.0(@aws-sdk/client-sso-oidc@3.637.0): resolution: {integrity: sha512-WvhW/7XSf+H7YmtiIigQxfDVZVZI7mbKikQ09YpzN7FeN3TmYib1+0tB+EE9TbICkwssjiFc71FEBEh4K9grKQ==} @@ -3734,7 +3694,7 @@ packages: '@aws-sdk/client-sso-oidc': 3.637.0(@aws-sdk/client-sts@3.609.0) '@aws-sdk/types': 3.609.0 '@smithy/property-provider': 3.1.3 - '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 dev: false @@ -3751,7 +3711,6 @@ packages: '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@aws-sdk/types@3.598.0: resolution: {integrity: sha512-742uRl6z7u0LFmZwDrFP6r1wlZcgVPw+/TilluDJmCAR8BgRw3IR+743kUXKBGd8QZDRW2n6v/PYsi/AWCDDMQ==} @@ -3767,7 +3726,6 @@ packages: dependencies: '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@aws-sdk/util-arn-parser@3.568.0: resolution: {integrity: sha512-XUKJWWo+KOB7fbnPP0+g/o5Ulku/X53t7i/h+sPHr5xxYTJJ9CYnbToo95mzxe7xWvkLrsNtJ8L+MnNn9INs2w==} @@ -3802,9 +3760,8 @@ packages: dependencies: '@aws-sdk/types': 3.609.0 '@smithy/types': 3.3.0 - '@smithy/util-endpoints': 2.0.4 + '@smithy/util-endpoints': 2.0.5 tslib: 2.6.3 - dev: false /@aws-sdk/util-endpoints@3.637.0: resolution: {integrity: sha512-pAqOKUHeVWHEXXDIp/qoMk/6jyxIb6GGjnK1/f8dKHtKIEs4tKsnnL563gceEvdad53OPXIt86uoevCcCzmBnw==} @@ -3814,7 +3771,6 @@ packages: '@smithy/types': 3.3.0 '@smithy/util-endpoints': 2.0.5 tslib: 2.6.3 - dev: false /@aws-sdk/util-format-url@3.609.0: resolution: {integrity: sha512-fuk29BI/oLQlJ7pfm6iJ4gkEpHdavffAALZwXh9eaY1vQ0ip0aKfRTiNudPoJjyyahnz5yJ1HkmlcDitlzsOrQ==} @@ -3831,7 +3787,6 @@ packages: engines: {node: '>=16.0.0'} dependencies: tslib: 2.6.3 - dev: false /@aws-sdk/util-user-agent-browser@3.598.0: resolution: {integrity: sha512-36Sxo6F+ykElaL1mWzWjlg+1epMpSe8obwhCN1yGE7Js9ywy5U6k6l+A3q3YM9YRbm740sNxncbwLklMvuhTKw==} @@ -3849,7 +3804,6 @@ packages: '@smithy/types': 3.3.0 bowser: 2.11.0 tslib: 2.6.3 - dev: false /@aws-sdk/util-user-agent-node@3.598.0: resolution: {integrity: sha512-oyWGcOlfTdzkC6SVplyr0AGh54IMrDxbhg5RxJ5P+V4BKfcDoDcZV9xenUk9NsOi9MuUjxMumb9UJGkDhM1m0A==} @@ -3876,10 +3830,9 @@ packages: optional: true dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/node-config-provider': 3.1.3 + '@smithy/node-config-provider': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@aws-sdk/util-user-agent-node@3.614.0: resolution: {integrity: sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==} @@ -3894,7 +3847,6 @@ packages: '@smithy/node-config-provider': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@aws-sdk/util-utf8-browser@3.259.0: resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} @@ -4716,7 +4668,6 @@ packages: dependencies: '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@smithy/chunked-blob-reader-native@3.0.0: resolution: {integrity: sha512-VDkpCYW+peSuM4zJip5WDfqvg2Mo/e8yxOv3VF1m11y7B8KKMKVFtmZWDe36Fvk8rGuWrPZHHXZ7rR7uM5yWyg==} @@ -4740,7 +4691,6 @@ packages: '@smithy/util-config-provider': 3.0.0 '@smithy/util-middleware': 3.0.3 tslib: 2.6.3 - dev: false /@smithy/config-resolver@3.0.5: resolution: {integrity: sha512-SkW5LxfkSI1bUC74OtfBbdz+grQXYiPYolyu8VfpLIjEoN/sHVBlLeGXMQ1vX4ejkgfv6sxVbQJ32yF2cl1veA==} @@ -4751,7 +4701,6 @@ packages: '@smithy/util-config-provider': 3.0.0 '@smithy/util-middleware': 3.0.3 tslib: 2.6.3 - dev: false /@smithy/core@2.2.4: resolution: {integrity: sha512-qdY3LpMOUyLM/gfjjMQZui+UTNS7kBRDWlvyIhVOql5dn2J3isk9qUTBtQ1CbDH8MTugHis1zu3h4rH+Qmmh4g==} @@ -4765,7 +4714,6 @@ packages: '@smithy/types': 3.3.0 '@smithy/util-middleware': 3.0.3 tslib: 2.6.3 - dev: false /@smithy/core@2.4.0: resolution: {integrity: sha512-cHXq+FneIF/KJbt4q4pjN186+Jf4ZB0ZOqEaZMBhT79srEyGDDBV31NqBRBjazz8ppQ1bJbDJMY9ba5wKFV36w==} @@ -4781,18 +4729,16 @@ packages: '@smithy/util-middleware': 3.0.3 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 - dev: false /@smithy/credential-provider-imds@3.1.3: resolution: {integrity: sha512-U1Yrv6hx/mRK6k8AncuI6jLUx9rn0VVSd9NPEX6pyYFBfkSkChOc/n4zUb8alHUVg83TbI4OdZVo1X0Zfj3ijA==} engines: {node: '>=16.0.0'} dependencies: - '@smithy/node-config-provider': 3.1.3 + '@smithy/node-config-provider': 3.1.4 '@smithy/property-provider': 3.1.3 '@smithy/types': 3.3.0 '@smithy/url-parser': 3.0.3 tslib: 2.6.3 - dev: false /@smithy/credential-provider-imds@3.2.0: resolution: {integrity: sha512-0SCIzgd8LYZ9EJxUjLXBmEKSZR/P/w6l7Rz/pab9culE/RWuqelAKGJvn5qUOl8BgX8Yj5HWM50A5hiB/RzsgA==} @@ -4803,7 +4749,6 @@ packages: '@smithy/types': 3.3.0 '@smithy/url-parser': 3.0.3 tslib: 2.6.3 - dev: false /@smithy/eventstream-codec@3.1.2: resolution: {integrity: sha512-0mBcu49JWt4MXhrhRAlxASNy0IjDRFU+aWNDRal9OtUJvJNiwDuyKMUONSOjLjSCeGwZaE0wOErdqULer8r7yw==} @@ -4857,7 +4802,6 @@ packages: '@smithy/types': 3.3.0 '@smithy/util-base64': 3.0.0 tslib: 2.6.3 - dev: false /@smithy/fetch-http-handler@3.2.4: resolution: {integrity: sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==} @@ -4867,7 +4811,6 @@ packages: '@smithy/types': 3.3.0 '@smithy/util-base64': 3.0.0 tslib: 2.6.3 - dev: false /@smithy/hash-blob-browser@3.1.2: resolution: {integrity: sha512-hAbfqN2UbISltakCC2TP0kx4LqXBttEv2MqSPE98gVuDFMf05lU+TpC41QtqGP3Ff5A3GwZMPfKnEy0VmEUpmg==} @@ -4886,7 +4829,6 @@ packages: '@smithy/util-buffer-from': 3.0.0 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 - dev: false /@smithy/hash-stream-node@3.1.2: resolution: {integrity: sha512-PBgDMeEdDzi6JxKwbfBtwQG9eT9cVwsf0dZzLXoJF4sHKHs5HEo/3lJWpn6jibfJwT34I1EBXpBnZE8AxAft6g==} @@ -4902,21 +4844,18 @@ packages: dependencies: '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@smithy/is-array-buffer@2.2.0: resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} engines: {node: '>=14.0.0'} dependencies: tslib: 2.6.3 - dev: false /@smithy/is-array-buffer@3.0.0: resolution: {integrity: sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==} engines: {node: '>=16.0.0'} dependencies: tslib: 2.6.3 - dev: false /@smithy/md5-js@3.0.3: resolution: {integrity: sha512-O/SAkGVwpWmelpj/8yDtsaVe6sINHLB1q8YE/+ZQbDxIw3SRLbTZuRaI10K12sVoENdnHqzPp5i3/H+BcZ3m3Q==} @@ -4933,7 +4872,6 @@ packages: '@smithy/protocol-http': 4.0.3 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@smithy/middleware-content-length@3.0.5: resolution: {integrity: sha512-ILEzC2eyxx6ncej3zZSwMpB5RJ0zuqH7eMptxC4KN3f+v9bqT8ohssKbhNR78k/2tWW+KS5Spw+tbPF4Ejyqvw==} @@ -4942,7 +4880,6 @@ packages: '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@smithy/middleware-endpoint@3.0.4: resolution: {integrity: sha512-whUJMEPwl3ANIbXjBXZVdJNgfV2ZU8ayln7xUM47rXL2txuenI7jQ/VFFwCzy5lCmXScjp6zYtptW5Evud8e9g==} @@ -4955,7 +4892,6 @@ packages: '@smithy/url-parser': 3.0.3 '@smithy/util-middleware': 3.0.3 tslib: 2.6.3 - dev: false /@smithy/middleware-endpoint@3.1.0: resolution: {integrity: sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==} @@ -4968,7 +4904,6 @@ packages: '@smithy/url-parser': 3.0.3 '@smithy/util-middleware': 3.0.3 tslib: 2.6.3 - dev: false /@smithy/middleware-retry@3.0.15: resolution: {integrity: sha512-iTMedvNt1ApdvkaoE8aSDuwaoc+BhvHqttbA/FO4Ty+y/S5hW6Ci/CTScG7vam4RYJWZxdTElc3MEfHRVH6cgQ==} @@ -4983,7 +4918,6 @@ packages: '@smithy/util-retry': 3.0.3 tslib: 2.6.3 uuid: 9.0.1 - dev: false /@smithy/middleware-retry@3.0.7: resolution: {integrity: sha512-f5q7Y09G+2h5ivkSx5CHvlAT4qRR3jBFEsfXyQ9nFNiWQlr8c48blnu5cmbTQ+p1xmIO14UXzKoF8d7Tm0Gsjw==} @@ -4998,7 +4932,6 @@ packages: '@smithy/util-retry': 3.0.3 tslib: 2.6.3 uuid: 9.0.1 - dev: false /@smithy/middleware-serde@3.0.3: resolution: {integrity: sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==} @@ -5006,7 +4939,6 @@ packages: dependencies: '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@smithy/middleware-stack@3.0.3: resolution: {integrity: sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==} @@ -5014,7 +4946,6 @@ packages: dependencies: '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@smithy/node-config-provider@3.1.3: resolution: {integrity: sha512-rxdpAZczzholz6CYZxtqDu/aKTxATD5DAUDVj7HoEulq+pDSQVWzbg0btZDlxeFfa6bb2b5tUvgdX5+k8jUqcg==} @@ -5024,7 +4955,6 @@ packages: '@smithy/shared-ini-file-loader': 3.1.3 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@smithy/node-config-provider@3.1.4: resolution: {integrity: sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ==} @@ -5034,7 +4964,6 @@ packages: '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@smithy/node-http-handler@3.1.1: resolution: {integrity: sha512-L71NLyPeP450r2J/mfu1jMc//Z1YnqJt2eSNw7uhiItaONnBLDA68J5jgxq8+MBDsYnFwNAIc7dBG1ImiWBiwg==} @@ -5045,7 +4974,6 @@ packages: '@smithy/querystring-builder': 3.0.3 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@smithy/node-http-handler@3.1.4: resolution: {integrity: sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==} @@ -5056,7 +4984,6 @@ packages: '@smithy/querystring-builder': 3.0.3 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@smithy/property-provider@3.1.3: resolution: {integrity: sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==} @@ -5064,7 +4991,6 @@ packages: dependencies: '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@smithy/protocol-http@4.0.3: resolution: {integrity: sha512-x5jmrCWwQlx+Zv4jAtc33ijJ+vqqYN+c/ZkrnpvEe/uDas7AT7A/4Rc2CdfxgWv4WFGmEqODIrrUToPN6DDkGw==} @@ -5072,7 +4998,6 @@ packages: dependencies: '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@smithy/protocol-http@4.1.0: resolution: {integrity: sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==} @@ -5080,7 +5005,6 @@ packages: dependencies: '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@smithy/querystring-builder@3.0.3: resolution: {integrity: sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==} @@ -5089,7 +5013,6 @@ packages: '@smithy/types': 3.3.0 '@smithy/util-uri-escape': 3.0.0 tslib: 2.6.3 - dev: false /@smithy/querystring-parser@3.0.3: resolution: {integrity: sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==} @@ -5097,14 +5020,12 @@ packages: dependencies: '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@smithy/service-error-classification@3.0.3: resolution: {integrity: sha512-Jn39sSl8cim/VlkLsUhRFq/dKDnRUFlfRkvhOJaUbLBXUsLRLNf9WaxDv/z9BjuQ3A6k/qE8af1lsqcwm7+DaQ==} engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.3.0 - dev: false /@smithy/shared-ini-file-loader@3.1.3: resolution: {integrity: sha512-Z8Y3+08vgoDgl4HENqNnnzSISAaGrF2RoKupoC47u2wiMp+Z8P/8mDh1CL8+8ujfi2U5naNvopSBmP/BUj8b5w==} @@ -5112,7 +5033,6 @@ packages: dependencies: '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@smithy/shared-ini-file-loader@3.1.4: resolution: {integrity: sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ==} @@ -5120,7 +5040,6 @@ packages: dependencies: '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@smithy/signature-v4@2.3.0: resolution: {integrity: sha512-ui/NlpILU+6HAQBfJX8BBsDXuKSNrjTSuOYArRblcrErwKFutjrCNb/OExfVRyj9+26F9J+ZmfWT+fKWuDrH3Q==} @@ -5146,7 +5065,6 @@ packages: '@smithy/util-uri-escape': 3.0.0 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 - dev: false /@smithy/signature-v4@4.1.0: resolution: {integrity: sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==} @@ -5160,7 +5078,6 @@ packages: '@smithy/util-uri-escape': 3.0.0 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 - dev: false /@smithy/smithy-client@3.1.5: resolution: {integrity: sha512-x9bL9Mx2CT2P1OiUlHM+ZNpbVU6TgT32f9CmTRzqIHA7M4vYrROCWEoC3o4xHNJASoGd4Opos3cXYPgh+/m4Ww==} @@ -5172,7 +5089,6 @@ packages: '@smithy/types': 3.3.0 '@smithy/util-stream': 3.0.5 tslib: 2.6.3 - dev: false /@smithy/smithy-client@3.2.0: resolution: {integrity: sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==} @@ -5184,7 +5100,6 @@ packages: '@smithy/types': 3.3.0 '@smithy/util-stream': 3.1.3 tslib: 2.6.3 - dev: false /@smithy/types@2.12.0: resolution: {integrity: sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==} @@ -5198,7 +5113,6 @@ packages: engines: {node: '>=16.0.0'} dependencies: tslib: 2.6.3 - dev: false /@smithy/url-parser@3.0.3: resolution: {integrity: sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==} @@ -5206,7 +5120,6 @@ packages: '@smithy/querystring-parser': 3.0.3 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@smithy/util-base64@3.0.0: resolution: {integrity: sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==} @@ -5215,20 +5128,17 @@ packages: '@smithy/util-buffer-from': 3.0.0 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 - dev: false /@smithy/util-body-length-browser@3.0.0: resolution: {integrity: sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==} dependencies: tslib: 2.6.3 - dev: false /@smithy/util-body-length-node@3.0.0: resolution: {integrity: sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==} engines: {node: '>=16.0.0'} dependencies: tslib: 2.6.3 - dev: false /@smithy/util-buffer-from@2.2.0: resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} @@ -5236,7 +5146,6 @@ packages: dependencies: '@smithy/is-array-buffer': 2.2.0 tslib: 2.6.3 - dev: false /@smithy/util-buffer-from@3.0.0: resolution: {integrity: sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==} @@ -5244,14 +5153,12 @@ packages: dependencies: '@smithy/is-array-buffer': 3.0.0 tslib: 2.6.3 - dev: false /@smithy/util-config-provider@3.0.0: resolution: {integrity: sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==} engines: {node: '>=16.0.0'} dependencies: tslib: 2.6.3 - dev: false /@smithy/util-defaults-mode-browser@3.0.15: resolution: {integrity: sha512-FZ4Psa3vjp8kOXcd3HJOiDPBCWtiilLl57r0cnNtq/Ga9RSDrM5ERL6xt+tO43+2af6Pn5Yp92x2n5vPuduNfg==} @@ -5262,7 +5169,6 @@ packages: '@smithy/types': 3.3.0 bowser: 2.11.0 tslib: 2.6.3 - dev: false /@smithy/util-defaults-mode-browser@3.0.7: resolution: {integrity: sha512-Q2txLyvQyGfmjsaDbVV7Sg8psefpFcrnlGapDzXGFRPFKRBeEg6OvFK8FljqjeHSaCZ6/UuzQExUPqBR/2qlDA==} @@ -5273,7 +5179,6 @@ packages: '@smithy/types': 3.3.0 bowser: 2.11.0 tslib: 2.6.3 - dev: false /@smithy/util-defaults-mode-node@3.0.15: resolution: {integrity: sha512-KSyAAx2q6d0t6f/S4XB2+3+6aQacm3aLMhs9aLMqn18uYGUepbdssfogW5JQZpc6lXNBnp0tEnR5e9CEKmEd7A==} @@ -5286,7 +5191,6 @@ packages: '@smithy/smithy-client': 3.2.0 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@smithy/util-defaults-mode-node@3.0.7: resolution: {integrity: sha512-F4Qcj1fG6MGi2BSWCslfsMSwllws/WzYONBGtLybyY+halAcXdWhcew+mej8M5SKd5hqPYp4f7b+ABQEaeytgg==} @@ -5299,7 +5203,6 @@ packages: '@smithy/smithy-client': 3.1.5 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@smithy/util-endpoints@2.0.4: resolution: {integrity: sha512-ZAtNf+vXAsgzgRutDDiklU09ZzZiiV/nATyqde4Um4priTmasDH+eLpp3tspL0hS2dEootyFMhu1Y6Y+tzpWBQ==} @@ -5308,7 +5211,6 @@ packages: '@smithy/node-config-provider': 3.1.3 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@smithy/util-endpoints@2.0.5: resolution: {integrity: sha512-ReQP0BWihIE68OAblC/WQmDD40Gx+QY1Ez8mTdFMXpmjfxSyz2fVQu3A4zXRfQU9sZXtewk3GmhfOHswvX+eNg==} @@ -5317,7 +5219,6 @@ packages: '@smithy/node-config-provider': 3.1.4 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@smithy/util-hex-encoding@2.2.0: resolution: {integrity: sha512-7iKXR+/4TpLK194pVjKiasIyqMtTYJsgKgM242Y9uzt5dhHnUDvMNb+3xIhRJ9QhvqGii/5cRUt4fJn3dtXNHQ==} @@ -5331,7 +5232,6 @@ packages: engines: {node: '>=16.0.0'} dependencies: tslib: 2.6.3 - dev: false /@smithy/util-middleware@2.2.0: resolution: {integrity: sha512-L1qpleXf9QD6LwLCJ5jddGkgWyuSvWBkJwWAZ6kFkdifdso+sk3L3O1HdmPvCdnCK3IS4qWyPxev01QMnfHSBw==} @@ -5347,7 +5247,6 @@ packages: dependencies: '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@smithy/util-retry@3.0.3: resolution: {integrity: sha512-AFw+hjpbtVApzpNDhbjNG5NA3kyoMs7vx0gsgmlJF4s+yz1Zlepde7J58zpIRIsdjc+emhpAITxA88qLkPF26w==} @@ -5356,7 +5255,6 @@ packages: '@smithy/service-error-classification': 3.0.3 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@smithy/util-stream@3.0.5: resolution: {integrity: sha512-xC3L5PKMAT/Bh8fmHNXP9sdQ4+4aKVUU3EEJ2CF/lLk7R+wtMJM+v/1B4en7jO++Wa5spGzFDBCl0QxgbUc5Ug==} @@ -5370,7 +5268,6 @@ packages: '@smithy/util-hex-encoding': 3.0.0 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 - dev: false /@smithy/util-stream@3.1.3: resolution: {integrity: sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==} @@ -5384,7 +5281,6 @@ packages: '@smithy/util-hex-encoding': 3.0.0 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 - dev: false /@smithy/util-uri-escape@2.2.0: resolution: {integrity: sha512-jtmJMyt1xMD/d8OtbVJ2gFZOSKc+ueYJZPW20ULW1GOp/q/YIM0wNh+u8ZFao9UaIGz4WoPW8hC64qlWLIfoDA==} @@ -5398,7 +5294,6 @@ packages: engines: {node: '>=16.0.0'} dependencies: tslib: 2.6.3 - dev: false /@smithy/util-utf8@2.3.0: resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} @@ -5406,7 +5301,6 @@ packages: dependencies: '@smithy/util-buffer-from': 2.2.0 tslib: 2.6.3 - dev: false /@smithy/util-utf8@3.0.0: resolution: {integrity: sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==} @@ -5414,7 +5308,6 @@ packages: dependencies: '@smithy/util-buffer-from': 3.0.0 tslib: 2.6.3 - dev: false /@smithy/util-waiter@3.1.2: resolution: {integrity: sha512-4pP0EV3iTsexDx+8PPGAKCQpd/6hsQBaQhqWzU4hqKPHN5epPsxKbvUTIiYIHTxaKt6/kEaqPBpu/ufvfbrRzw==} @@ -5423,7 +5316,6 @@ packages: '@smithy/abort-controller': 3.1.1 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@testcontainers/postgresql@10.9.0: resolution: {integrity: sha512-Z3K/TFkl/PVE2v8A6yKqgF4pSFk9ilFG02yeGhPswUjmBlcig/rpVOjBQOkQ/yJCcQ/r2RrX3RR+7vr+UO4QlQ==} @@ -6246,7 +6138,6 @@ packages: /bowser@2.11.0: resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} - dev: false /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -7502,14 +7393,12 @@ packages: hasBin: true dependencies: strnum: 1.0.5 - dev: false /fast-xml-parser@4.4.1: resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==} hasBin: true dependencies: strnum: 1.0.5 - dev: false /fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} @@ -8641,7 +8530,6 @@ packages: resolution: {integrity: sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==} dependencies: obliterator: 1.6.1 - dev: false /mongodb-connection-string-url@3.0.1: resolution: {integrity: sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==} @@ -8828,7 +8716,6 @@ packages: /obliterator@1.6.1: resolution: {integrity: sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==} - dev: false /on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} @@ -9872,7 +9759,6 @@ packages: /strnum@1.0.5: resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} - dev: false /subarg@1.0.0: resolution: {integrity: sha512-RIrIdRY0X1xojthNcVtgT9sjpOGagEUKpZdgBUi054OEPFo282yg+zE+t1Rj3+RqKq2xStL7uUHhY+AjbC4BXg==} @@ -10345,7 +10231,6 @@ packages: /uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true - dev: false /v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} From f6e9a080995b09eb89698b2acb8e4e838e1f9424 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 10:26:55 +0200 Subject: [PATCH 200/241] Update model --- packages/catalog-platformstate-writer/src/utils.ts | 3 +++ .../src/token-generation-readmodel/platform-states-entry.ts | 1 + 2 files changed, 4 insertions(+) diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index be07a485bd..9f0cd510e9 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -30,6 +30,9 @@ export const writeCatalogEntry = async ( descriptorAudience: { S: catalogEntry.descriptorAudience, }, + descriptorVoucherLifespan: { + N: catalogEntry.descriptorVoucherLifespan.toString(), + }, version: { N: catalogEntry.version.toString(), }, diff --git a/packages/models/src/token-generation-readmodel/platform-states-entry.ts b/packages/models/src/token-generation-readmodel/platform-states-entry.ts index 52bc81ba1a..e2be636935 100644 --- a/packages/models/src/token-generation-readmodel/platform-states-entry.ts +++ b/packages/models/src/token-generation-readmodel/platform-states-entry.ts @@ -33,6 +33,7 @@ type PlatformStatesBaseEntry = z.infer; export const PlatformStatesCatalogEntry = PlatformStatesBaseEntry.extend({ PK: PlatformStatesEServiceDescriptorPK, descriptorAudience: z.string(), + descriptorVoucherLifespan: z.number(), }); export type PlatformStatesCatalogEntry = z.infer< typeof PlatformStatesCatalogEntry From 5dbf0bda17452234394e1535afe4d756e510acac Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 10:31:26 +0200 Subject: [PATCH 201/241] Update platform-states table --- docker/dynamoDB-tables/platform-states.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docker/dynamoDB-tables/platform-states.json b/docker/dynamoDB-tables/platform-states.json index 11d3e645a0..44a456e5b4 100644 --- a/docker/dynamoDB-tables/platform-states.json +++ b/docker/dynamoDB-tables/platform-states.json @@ -1,7 +1,20 @@ { "TableName": "platform-states", - "AttributeDefinitions": [{ "AttributeName": "PK", "AttributeType": "S" }], + "AttributeDefinitions": [ + { "AttributeName": "PK", "AttributeType": "S" }, + { "AttributeName": "GSIPK_consumerId_eserviceId", "AttributeType": "S" } + ], "KeySchema": [{ "AttributeName": "PK", "KeyType": "HASH" }], + "GlobalSecondaryIndexes": [ + { + "IndexName": "GSIPK_consumerId_eserviceId", + "KeySchema": [ + { "AttributeName": "GSIPK_consumerId_eserviceId", "KeyType": "HASH" }, + { "AttributeName": "GSISK_agreementTimestamp", "KeyType": "HASH" } + ], + "Projection": { "ProjectionType": "ALL" } + } + ], "ProvisionedThroughput": { "ReadCapacityUnits": 10, "WriteCapacityUnits": 5 From 3d9664b3dc8d85b5fb430fed4cdffa2a0af4ea3e Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 10:32:15 +0200 Subject: [PATCH 202/241] Fix --- docker/dynamoDB-tables/platform-states.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/dynamoDB-tables/platform-states.json b/docker/dynamoDB-tables/platform-states.json index 44a456e5b4..5a7ffb807d 100644 --- a/docker/dynamoDB-tables/platform-states.json +++ b/docker/dynamoDB-tables/platform-states.json @@ -2,7 +2,8 @@ "TableName": "platform-states", "AttributeDefinitions": [ { "AttributeName": "PK", "AttributeType": "S" }, - { "AttributeName": "GSIPK_consumerId_eserviceId", "AttributeType": "S" } + { "AttributeName": "GSIPK_consumerId_eserviceId", "AttributeType": "S" }, + { "AttributeName": "GSISK_agreementTimestamp", "AttributeType": "S" } ], "KeySchema": [{ "AttributeName": "PK", "KeyType": "HASH" }], "GlobalSecondaryIndexes": [ From 4f9904a09630c673139d90076aea5d40f2bf017b Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 10:35:40 +0200 Subject: [PATCH 203/241] Update token-generation-states table --- .../token-generation-states.json | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/docker/dynamoDB-tables/token-generation-states.json b/docker/dynamoDB-tables/token-generation-states.json index e2b53457ca..590c808678 100644 --- a/docker/dynamoDB-tables/token-generation-states.json +++ b/docker/dynamoDB-tables/token-generation-states.json @@ -2,7 +2,12 @@ "TableName": "token-generation-states", "AttributeDefinitions": [ { "AttributeName": "PK", "AttributeType": "S" }, - { "AttributeName": "GSIPK_eserviceId_descriptorId", "AttributeType": "S" } + { "AttributeName": "GSIPK_eserviceId_descriptorId", "AttributeType": "S" }, + { "AttributeName": "GSIPK_consumerId_eserviceId", "AttributeType": "S" }, + { "AttributeName": "GSIPK_purposeId", "AttributeType": "S" }, + { "AttributeName": "GSIPK_clientId", "AttributeType": "S" }, + { "AttributeName": "GSIPK_kid", "AttributeType": "S" }, + { "AttributeName": "GSIPK_clientId_purposeId", "AttributeType": "S" } ], "KeySchema": [{ "AttributeName": "PK", "KeyType": "HASH" }], "GlobalSecondaryIndexes": [ @@ -12,6 +17,38 @@ { "AttributeName": "GSIPK_eserviceId_descriptorId", "KeyType": "HASH" } ], "Projection": { "ProjectionType": "ALL" } + }, + { + "IndexName": "GSIPK_consumerId_eserviceId", + "KeySchema": [ + { + "AttributeName": "GSIPK_consumerId_eserviceId", + "KeyType": "HASH" + } + ], + "Projection": { "ProjectionType": "ALL" } + }, + { + "IndexName": "GSIPK_purposeId", + "KeySchema": [{ "AttributeName": "GSIPK_purposeId", "KeyType": "HASH" }], + "Projection": { "ProjectionType": "ALL" } + }, + { + "IndexName": "GSIPK_clientId", + "KeySchema": [{ "AttributeName": "GSIPK_clientId", "KeyType": "HASH" }], + "Projection": { "ProjectionType": "ALL" } + }, + { + "IndexName": "GSIPK_kid", + "KeySchema": [{ "AttributeName": "GSIPK_kid", "KeyType": "HASH" }], + "Projection": { "ProjectionType": "ALL" } + }, + { + "IndexName": "GSIPK_clientId_purposeId", + "KeySchema": [ + { "AttributeName": "GSIPK_clientId_purposeId", "KeyType": "HASH" } + ], + "Projection": { "ProjectionType": "ALL" } } ], "ProvisionedThroughput": { From abc158a6eb4a5dd84d93e1b533be5d0dfd101284 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 10:37:17 +0200 Subject: [PATCH 204/241] Update aws config --- packages/catalog-platformstate-writer/aws.config.local | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/catalog-platformstate-writer/aws.config.local b/packages/catalog-platformstate-writer/aws.config.local index c2cb4f4115..871616b32d 100644 --- a/packages/catalog-platformstate-writer/aws.config.local +++ b/packages/catalog-platformstate-writer/aws.config.local @@ -1,4 +1,4 @@ [default] -aws_access_key_id=test-aws-key -aws_secret_access_key=test-aws-secret +aws_access_key_id=key +aws_secret_access_key=secret region=eu-central-1 From 8b6db83859d6a7af7464509aa171280c5ddaa458 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Tue, 24 Sep 2024 10:41:10 +0200 Subject: [PATCH 205/241] Rename descriptorStateToItemState function --- .../catalog-platformstate-writer/src/consumerServiceV2.ts | 8 ++++---- packages/catalog-platformstate-writer/src/utils.ts | 6 ++---- .../test/catalogPlatformstateWriter.integration.test.ts | 6 +++--- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index b802fae492..9eeb67716b 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -16,7 +16,7 @@ import { import { match } from "ts-pattern"; import { deleteCatalogEntry, - descriptorStateToClientState, + descriptorStateToItemState, readCatalogEntry, updateDescriptorStateInPlatformStatesEntry, updateDescriptorStateInTokenGenerationStatesTable, @@ -60,7 +60,7 @@ export async function handleMessageV2( await updateDescriptorStateInPlatformStatesEntry( dynamoDBClient, primaryKeyCurrent, - descriptorStateToClientState(descriptor.state), + descriptorStateToItemState(descriptor.state), msg.version ); @@ -77,7 +77,7 @@ export async function handleMessageV2( } else { const catalogEntry: PlatformStatesCatalogEntry = { PK: primaryKeyCurrent, - state: descriptorStateToClientState(descriptor.state), + state: descriptorStateToItemState(descriptor.state), descriptorAudience: descriptor.audience[0], version: msg.version, updatedAt: new Date().toISOString(), @@ -148,7 +148,7 @@ export async function handleMessageV2( await updateDescriptorStateInPlatformStatesEntry( dynamoDBClient, primaryKey, - descriptorStateToClientState(descriptor.state), + descriptorStateToItemState(descriptor.state), msg.version ); diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index f7da70602b..caa9e04a40 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -102,9 +102,7 @@ export const deleteCatalogEntry = async ( await dynamoDBClient.send(command); }; -export const descriptorStateToClientState = ( - state: DescriptorState -): ItemState => +export const descriptorStateToItemState = (state: DescriptorState): ItemState => state === descriptorState.published || state === descriptorState.deprecated ? itemState.active : itemState.inactive; @@ -228,7 +226,7 @@ export const updateDescriptorStateInTokenGenerationStatesTable = async ( }, ExpressionAttributeValues: { ":newState": { - S: descriptorStateToClientState(descriptorState), + S: descriptorStateToItemState(descriptorState), }, ":newUpdateAt": { S: new Date().toISOString(), diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 5fda2329ab..88c9d3b95a 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -44,7 +44,7 @@ import { } from "pagopa-interop-commons-test"; import { deleteCatalogEntry, - descriptorStateToClientState, + descriptorStateToItemState, readCatalogEntry, readTokenStateEntriesByEserviceIdAndDescriptorId, updateDescriptorStateInPlatformStatesEntry, @@ -317,7 +317,7 @@ describe("integration tests", async () => { it.each([descriptorState.published, descriptorState.deprecated])( "should convert %s state to active", async (s) => { - expect(descriptorStateToClientState(s)).toBe(itemState.active); + expect(descriptorStateToItemState(s)).toBe(itemState.active); } ); @@ -326,7 +326,7 @@ describe("integration tests", async () => { descriptorState.draft, descriptorState.suspended, ])("should convert %s state to inactive", async (s) => { - expect(descriptorStateToClientState(s)).toBe(itemState.inactive); + expect(descriptorStateToItemState(s)).toBe(itemState.inactive); }); }); From 3d12bff0c04d6346744bb8159635a1f9211c24de Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 10:44:22 +0200 Subject: [PATCH 206/241] Update key types usage --- packages/catalog-platformstate-writer/src/utils.ts | 5 +++-- packages/models/src/index.ts | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index 9f0cd510e9..c6c03ed13b 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -1,6 +1,7 @@ import { genericInternalError, PlatformStatesCatalogEntry, + PlatformStatesEServiceDescriptorPK, } from "pagopa-interop-models"; import { DeleteItemCommand, @@ -47,7 +48,7 @@ export const writeCatalogEntry = async ( }; export const readCatalogEntry = async ( - primaryKey: string, + primaryKey: PlatformStatesEServiceDescriptorPK, dynamoDBClient: DynamoDBClient ): Promise => { const input: GetItemInput = { @@ -77,7 +78,7 @@ export const readCatalogEntry = async ( }; export const deleteCatalogEntry = async ( - primaryKey: string, + primaryKey: PlatformStatesEServiceDescriptorPK, dynamoDBClient: DynamoDBClient ): Promise => { const input: DeleteItemInput = { diff --git a/packages/models/src/index.ts b/packages/models/src/index.ts index 121901ace4..bc0d0405d0 100644 --- a/packages/models/src/index.ts +++ b/packages/models/src/index.ts @@ -54,6 +54,7 @@ export * from "./user/user.js"; export * from "./token-generation-readmodel/platform-states-entry.js"; export * from "./token-generation-readmodel/token-generation-states-entry.js"; +export * from "./token-generation-readmodel/dynamoDB-keys.js"; // Protobuf export * from "./protobuf/protobuf.js"; From 6ad6e6ff830659d566c85d47779ec65ff2bbcbee Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 10:55:30 +0200 Subject: [PATCH 207/241] Fix --- .../src/consumerServiceV2.ts | 1 + .../catalogPlatformstateWriter.integration.test.ts | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index 9eeb67716b..9cc512d3bb 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -79,6 +79,7 @@ export async function handleMessageV2( PK: primaryKeyCurrent, state: descriptorStateToItemState(descriptor.state), descriptorAudience: descriptor.audience[0], + descriptorVoucherLifespan: descriptor.voucherLifespan, version: msg.version, updatedAt: new Date().toISOString(), }; diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index ea6edbf0c1..a4528f7125 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -114,6 +114,7 @@ describe("integration tests", async () => { PK: primaryKey, state: itemState.inactive, descriptorAudience: "pagopa.it", + descriptorVoucherLifespan: 60, version: 1, updatedAt: new Date().toISOString(), }; @@ -150,6 +151,7 @@ describe("integration tests", async () => { PK: primaryKey, state: itemState.inactive, descriptorAudience: "pagopa.it", + descriptorVoucherLifespan: 100, version: 1, updatedAt: new Date().toISOString(), }; @@ -167,6 +169,7 @@ describe("integration tests", async () => { const catalogStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, state: itemState.inactive, + descriptorVoucherLifespan: 100, descriptorAudience: "pagopa.it", version: 1, updatedAt: new Date().toISOString(), @@ -203,6 +206,7 @@ describe("integration tests", async () => { PK: primaryKey, state: itemState.inactive, descriptorAudience: "pagopa.it", + descriptorVoucherLifespan: 100, version: 1, updatedAt: new Date().toISOString(), }; @@ -236,6 +240,7 @@ describe("integration tests", async () => { PK: primaryKey, state: itemState.inactive, descriptorAudience: "pagopa.it", + descriptorVoucherLifespan: 100, version: 1, updatedAt: new Date().toISOString(), }; @@ -541,6 +546,7 @@ describe("integration tests", async () => { PK: catalogEntryPrimaryKey, state: itemState.inactive, descriptorAudience: publishedDescriptor.audience[0], + descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, version: 2, updatedAt: new Date().toISOString(), }; @@ -639,6 +645,7 @@ describe("integration tests", async () => { PK: catalogEntryPrimaryKey, state: itemState.inactive, descriptorAudience: publishedDescriptor.audience[0], + descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, version: 1, updatedAt: new Date().toISOString(), }; @@ -833,6 +840,7 @@ describe("integration tests", async () => { PK: primaryKey, state: itemState.inactive, descriptorAudience: archivedDescriptor.audience[0], + descriptorVoucherLifespan: archivedDescriptor.voucherLifespan, version: 1, updatedAt: new Date().toISOString(), }; @@ -946,6 +954,7 @@ describe("integration tests", async () => { PK: primaryKey, state: itemState.active, descriptorAudience: publishedDescriptor.audience[0], + descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, version: 2, updatedAt: new Date().toISOString(), }; @@ -988,6 +997,7 @@ describe("integration tests", async () => { PK: primaryKey, state: itemState.active, descriptorAudience: publishedDescriptor.audience[0], + descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, version: 2, updatedAt: new Date().toISOString(), }; @@ -1034,6 +1044,7 @@ describe("integration tests", async () => { PK: primaryKey, state: itemState.inactive, descriptorAudience: publishedDescriptor.audience[0], + descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, version: 2, updatedAt: new Date().toISOString(), }; @@ -1191,6 +1202,7 @@ describe("integration tests", async () => { PK: primaryKey, state: itemState.active, descriptorAudience: suspendedDescriptor.audience[0], + descriptorVoucherLifespan: suspendedDescriptor.voucherLifespan, version: 2, updatedAt: new Date().toISOString(), }; @@ -1288,6 +1300,7 @@ describe("integration tests", async () => { PK: primaryKey, state: itemState.active, descriptorAudience: suspendedDescriptor.audience[0], + descriptorVoucherLifespan: suspendedDescriptor.voucherLifespan, version: 1, updatedAt: new Date().toISOString(), }; From b9b3c4b19c2b8805806955d66bbc1ed8aa309a5c Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 11:00:15 +0200 Subject: [PATCH 208/241] Fix test split --- .../test/keys.test.ts | 56 ++ .../test/utils.test.ts | 513 ++++++++++++++++-- 2 files changed, 532 insertions(+), 37 deletions(-) create mode 100644 packages/catalog-platformstate-writer/test/keys.test.ts diff --git a/packages/catalog-platformstate-writer/test/keys.test.ts b/packages/catalog-platformstate-writer/test/keys.test.ts new file mode 100644 index 0000000000..ce361efe04 --- /dev/null +++ b/packages/catalog-platformstate-writer/test/keys.test.ts @@ -0,0 +1,56 @@ +import { + ClientId, + DescriptorId, + EServiceId, + generateId, + makeGSIPKEServiceIdDescriptorId, + makePlatformStatesEServiceDescriptorPK, + makeTokenGenerationStatesClientKidPK, + makeTokenGenerationStatesClientKidPurposePK, + PurposeId, +} from "pagopa-interop-models"; +import { describe, expect, it } from "vitest"; + +describe("keys test", () => { + it("makePlatformStatesEServiceDescriptorPK", () => { + const eserviceId = generateId(); + const descriptorId = generateId(); + const PK = makePlatformStatesEServiceDescriptorPK({ + eserviceId, + descriptorId, + }); + expect(PK).toEqual(`ESERVICEDESCRIPTOR#${eserviceId}#${descriptorId}`); + }); + + it("makeGSIPKEServiceIdDescriptorId", () => { + const eserviceId = generateId(); + const descriptorId = generateId(); + const GSI = makeGSIPKEServiceIdDescriptorId({ + eserviceId, + descriptorId, + }); + expect(GSI).toEqual(`${eserviceId}#${descriptorId}`); + }); + + it("makeTokenGenerationStatesClientKidPurposePK", () => { + const clientId = generateId(); + const kid = `kid ${Math.random()}`; + const purposeId = generateId(); + const PK = makeTokenGenerationStatesClientKidPurposePK({ + clientId, + kid, + purposeId, + }); + expect(PK).toEqual(`CLIENTKIDPURPOSE#${clientId}#${kid}#${purposeId}`); + }); + + it("makeTokenGenerationStatesClientKidPK", () => { + const clientId = generateId(); + const kid = `kid ${Math.random()}`; + const PK = makeTokenGenerationStatesClientKidPK({ + clientId, + kid, + }); + expect(PK).toEqual(`CLIENTKID#${clientId}#${kid}`); + }); +}); diff --git a/packages/catalog-platformstate-writer/test/utils.test.ts b/packages/catalog-platformstate-writer/test/utils.test.ts index cf1eaa6d64..e96746af6e 100644 --- a/packages/catalog-platformstate-writer/test/utils.test.ts +++ b/packages/catalog-platformstate-writer/test/utils.test.ts @@ -1,56 +1,495 @@ +/* eslint-disable @typescript-eslint/no-floating-promises */ +import { fail } from "assert"; import { - ClientId, - DescriptorId, - EServiceId, + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, + vi, +} from "vitest"; +import { + PlatformStatesCatalogEntry, + TokenGenerationStatesClientPurposeEntry, + descriptorState, generateId, + itemState, makeGSIPKEServiceIdDescriptorId, makePlatformStatesEServiceDescriptorPK, - makeTokenGenerationStatesClientKidPK, makeTokenGenerationStatesClientKidPurposePK, - PurposeId, } from "pagopa-interop-models"; -import { describe, expect, it } from "vitest"; +import { + ConditionalCheckFailedException, + DynamoDBClient, +} from "@aws-sdk/client-dynamodb"; +import { + getMockTokenStatesClientPurposeEntry, + buildDynamoDBTables, + deleteDynamoDBTables, +} from "pagopa-interop-commons-test"; +import { + deleteCatalogEntry, + descriptorStateToItemState, + readCatalogEntry, + readTokenStateEntriesByEserviceIdAndDescriptorId, + updateDescriptorStateInPlatformStatesEntry, + updateDescriptorStateInTokenGenerationStatesTable, + writeCatalogEntry, +} from "../src/utils.js"; +import { + config, + readAllTokenStateItems, + writeTokenStateEntry, +} from "./utils.js"; + +describe("utils tests", async () => { + if (!config) { + fail(); + } + const dynamoDBClient = new DynamoDBClient({ + credentials: { accessKeyId: "key", secretAccessKey: "secret" }, + region: "eu-central-1", + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + endpoint: `http://${config.tokenGenerationReadModelDbHost}:${ + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + config.tokenGenerationReadModelDbPort + }`, + }); + beforeEach(async () => { + await buildDynamoDBTables(dynamoDBClient); + }); + afterEach(async () => { + await deleteDynamoDBTables(dynamoDBClient); + }); + const mockDate = new Date(); + beforeAll(() => { + vi.useFakeTimers(); + vi.setSystemTime(mockDate); + }); + afterAll(() => { + vi.useRealTimers(); + }); -describe("test", () => { - it("makePlatformStatesEServiceDescriptorPK", () => { - const eserviceId = generateId(); - const descriptorId = generateId(); - const PK = makePlatformStatesEServiceDescriptorPK({ - eserviceId, - descriptorId, + describe("updateDescriptorStateInPlatformStatesEntry", async () => { + it("should throw error if previous entry doesn't exist", async () => { + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + expect( + updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, + primaryKey, + itemState.active, + 1 + ) + ).rejects.toThrowError(ConditionalCheckFailedException); + const catalogEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + expect(catalogEntry).toBeUndefined(); + }); + + it("should update state if previous entry exists", async () => { + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const previousCatalogStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.inactive, + descriptorAudience: "pagopa.it", + descriptorVoucherLifespan: 60, + version: 1, + updatedAt: new Date().toISOString(), + }; + expect( + await readCatalogEntry(primaryKey, dynamoDBClient) + ).toBeUndefined(); + await writeCatalogEntry(previousCatalogStateEntry, dynamoDBClient); + await updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, + primaryKey, + itemState.active, + 2 + ); + + const result = await readCatalogEntry(primaryKey, dynamoDBClient); + const expectedCatalogEntry: PlatformStatesCatalogEntry = { + ...previousCatalogStateEntry, + state: itemState.active, + version: 2, + updatedAt: new Date().toISOString(), + }; + + expect(result).toEqual(expectedCatalogEntry); }); - expect(PK).toEqual(`ESERVICEDESCRIPTOR#${eserviceId}#${descriptorId}`); }); - it("makeGSIPKEServiceIdDescriptorId", () => { - const eserviceId = generateId(); - const descriptorId = generateId(); - const GSI = makeGSIPKEServiceIdDescriptorId({ - eserviceId, - descriptorId, + describe("writeCatalogEntry", async () => { + it("should throw error if previous entry exists", async () => { + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const catalogEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.inactive, + descriptorAudience: "pagopa.it", + descriptorVoucherLifespan: 100, + version: 1, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(catalogEntry, dynamoDBClient); + expect( + writeCatalogEntry(catalogEntry, dynamoDBClient) + ).rejects.toThrowError(ConditionalCheckFailedException); + }); + + it("should write if previous entry doesn't exist", async () => { + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const catalogStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.inactive, + descriptorVoucherLifespan: 100, + descriptorAudience: "pagopa.it", + version: 1, + updatedAt: new Date().toISOString(), + }; + expect( + await readCatalogEntry(primaryKey, dynamoDBClient) + ).toBeUndefined(); + await writeCatalogEntry(catalogStateEntry, dynamoDBClient); + const retrievedCatalogEntry = await readCatalogEntry( + primaryKey, + dynamoDBClient + ); + + expect(retrievedCatalogEntry).toEqual(catalogStateEntry); }); - expect(GSI).toEqual(`${eserviceId}#${descriptorId}`); }); - it("makeTokenGenerationStatesClientKidPurposePK", () => { - const clientId = generateId(); - const kid = `kid ${Math.random()}`; - const purposeId = generateId(); - const PK = makeTokenGenerationStatesClientKidPurposePK({ - clientId, - kid, - purposeId, + describe("readCatalogEntry", async () => { + it("should return undefined if entry doesn't exist", async () => { + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const catalogEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + expect(catalogEntry).toBeUndefined(); + }); + + it("should return entry if it exists", async () => { + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const previousCatalogStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.inactive, + descriptorAudience: "pagopa.it", + descriptorVoucherLifespan: 100, + version: 1, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousCatalogStateEntry, dynamoDBClient); + const retrievedCatalogEntry = await readCatalogEntry( + primaryKey, + dynamoDBClient + ); + + expect(retrievedCatalogEntry).toEqual(previousCatalogStateEntry); }); - expect(PK).toEqual(`CLIENTKIDPURPOSE#${clientId}#${kid}#${purposeId}`); }); - it("makeTokenGenerationStatesClientKidPK", () => { - const clientId = generateId(); - const kid = `kid ${Math.random()}`; - const PK = makeTokenGenerationStatesClientKidPK({ - clientId, - kid, + describe("deleteCatalogEntry", async () => { + it("should not throw error if previous entry doesn't exist", async () => { + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + expect( + deleteCatalogEntry(primaryKey, dynamoDBClient) + ).resolves.not.toThrowError(); + }); + + it("should delete the entry if it exists", async () => { + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const previousCatalogStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.inactive, + descriptorAudience: "pagopa.it", + descriptorVoucherLifespan: 100, + version: 1, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousCatalogStateEntry, dynamoDBClient); + await deleteCatalogEntry(primaryKey, dynamoDBClient); + const retrievedCatalogEntry = await readCatalogEntry( + primaryKey, + dynamoDBClient + ); + expect(retrievedCatalogEntry).toBeUndefined(); + }); + }); + + describe("descriptorStateToClientState", async () => { + it.each([descriptorState.published, descriptorState.deprecated])( + "should convert %s state to active", + async (s) => { + expect(descriptorStateToItemState(s)).toBe(itemState.active); + } + ); + + it.each([ + descriptorState.archived, + descriptorState.draft, + descriptorState.suspended, + ])("should convert %s state to inactive", async (s) => { + expect(descriptorStateToItemState(s)).toBe(itemState.inactive); + }); + }); + + // token-generation-states + describe("writeTokenStateEntry", async () => { + it("should throw error if previous entry exists", async () => { + const tokenStateEntryPK = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const tokenStateEntry: TokenGenerationStatesClientPurposeEntry = { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK), + descriptorState: itemState.inactive, + descriptorAudience: "pagopa.it", + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(tokenStateEntry, dynamoDBClient); + expect( + writeTokenStateEntry(tokenStateEntry, dynamoDBClient) + ).rejects.toThrowError(ConditionalCheckFailedException); + }); + + it("should write if previous entry doesn't exist", async () => { + const tokenStateEntryPK = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const previousTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + expect(previousTokenStateEntries).toEqual([]); + const tokenStateEntry: TokenGenerationStatesClientPurposeEntry = { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK), + descriptorState: itemState.inactive, + descriptorAudience: "pagopa.it", + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(tokenStateEntry, dynamoDBClient); + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + + expect(retrievedTokenStateEntries).toEqual([tokenStateEntry]); + }); + }); + + describe("readTokenStateEntriesByEserviceIdAndDescriptorId", async () => { + it("should return empty array if entries do not exist", async () => { + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const result = await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + expect(result).toEqual([]); + }); + + it("should return entries if they exist (no need for pagination)", async () => { + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const tokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: "pagopa.it", + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(tokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const tokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: "pagopa.it", + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(tokenStateEntry2, dynamoDBClient); + + const tokenEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + + expect(tokenEntries).toEqual( + expect.arrayContaining([tokenStateEntry1, tokenStateEntry2]) + ); + }); + + it("should return entries if they exist (with pagination)", async () => { + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + + const tokenEntriesLength = 2000; + + const writtenEntries: TokenGenerationStatesClientPurposeEntry[] = []; + // eslint-disable-next-line functional/no-let + for (let i = 0; i < tokenEntriesLength; i++) { + const tokenStateEntryPK = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const tokenStateEntry: TokenGenerationStatesClientPurposeEntry = { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK), + descriptorState: itemState.inactive, + descriptorAudience: "pagopa.it", + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(tokenStateEntry, dynamoDBClient); + // eslint-disable-next-line functional/immutable-data + writtenEntries.push(tokenStateEntry); + } + vi.spyOn(dynamoDBClient, "send"); + const tokenEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + + expect(dynamoDBClient.send).toHaveBeenCalledTimes(2); + expect(tokenEntries).toHaveLength(tokenEntriesLength); + expect(tokenEntries).toEqual(expect.arrayContaining(writtenEntries)); + }); + }); + + describe("updateDescriptorStateInTokenGenerationStatesTable", async () => { + it("should do nothing if previous entry doesn't exist", async () => { + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const tokenStateEntries = await readAllTokenStateItems(dynamoDBClient); + expect(tokenStateEntries).toEqual([]); + expect( + updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptorState.archived, + dynamoDBClient + ) + ).resolves.not.toThrowError(); + const tokenStateEntriesAfterUpdate = await readAllTokenStateItems( + dynamoDBClient + ); + expect(tokenStateEntriesAfterUpdate).toEqual([]); + }); + + it("should update state if previous entries exist", async () => { + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: generateId(), + descriptorId: generateId(), + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: "pagopa.it", + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: "pagopa.it", + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptorState.published, + dynamoDBClient + ); + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: itemState.active, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: itemState.active, + updatedAt: new Date().toISOString(), + }; + + expect(retrievedTokenStateEntries).toHaveLength(2); + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry2, + expectedTokenStateEntry1, + ]) + ); }); - expect(PK).toEqual(`CLIENTKID#${clientId}#${kid}`); }); }); From 80fd523d7bf3e9a992adf0ffe9e0d16fc0a07c71 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Tue, 24 Sep 2024 11:00:18 +0200 Subject: [PATCH 209/241] Remove describe utils in test file --- ...logPlatformstateWriter.integration.test.ts | 442 +----------------- 1 file changed, 2 insertions(+), 440 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index a4528f7125..59fcd735c6 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -28,10 +28,7 @@ import { makeTokenGenerationStatesClientKidPurposePK, toEServiceV2, } from "pagopa-interop-models"; -import { - ConditionalCheckFailedException, - DynamoDBClient, -} from "@aws-sdk/client-dynamodb"; +import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; import { getMockDescriptor, getMockEService, @@ -41,21 +38,12 @@ import { deleteDynamoDBTables, } from "pagopa-interop-commons-test"; import { - deleteCatalogEntry, - descriptorStateToItemState, readCatalogEntry, readTokenStateEntriesByEserviceIdAndDescriptorId, - updateDescriptorStateInPlatformStatesEntry, - updateDescriptorStateInTokenGenerationStatesTable, writeCatalogEntry, } from "../src/utils.js"; import { handleMessageV2 } from "../src/consumerServiceV2.js"; -import { - config, - readAllTokenStateItems, - sleep, - writeTokenStateEntry, -} from "./utils.js"; +import { config, sleep, writeTokenStateEntry } from "./utils.js"; describe("integration tests", async () => { if (!config) { @@ -85,432 +73,6 @@ describe("integration tests", async () => { vi.useRealTimers(); }); - describe("utils", async () => { - // TODO: move this to other test file after improving table setup - describe("updateDescriptorStateInPlatformStatesEntry", async () => { - it("should throw error if previous entry doesn't exist", async () => { - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: generateId(), - descriptorId: generateId(), - }); - expect( - updateDescriptorStateInPlatformStatesEntry( - dynamoDBClient, - primaryKey, - itemState.active, - 1 - ) - ).rejects.toThrowError(ConditionalCheckFailedException); - const catalogEntry = await readCatalogEntry(primaryKey, dynamoDBClient); - expect(catalogEntry).toBeUndefined(); - }); - - it("should update state if previous entry exists", async () => { - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: generateId(), - descriptorId: generateId(), - }); - const previousCatalogStateEntry: PlatformStatesCatalogEntry = { - PK: primaryKey, - state: itemState.inactive, - descriptorAudience: "pagopa.it", - descriptorVoucherLifespan: 60, - version: 1, - updatedAt: new Date().toISOString(), - }; - expect( - await readCatalogEntry(primaryKey, dynamoDBClient) - ).toBeUndefined(); - await writeCatalogEntry(previousCatalogStateEntry, dynamoDBClient); - await updateDescriptorStateInPlatformStatesEntry( - dynamoDBClient, - primaryKey, - itemState.active, - 2 - ); - - const result = await readCatalogEntry(primaryKey, dynamoDBClient); - const expectedCatalogEntry: PlatformStatesCatalogEntry = { - ...previousCatalogStateEntry, - state: itemState.active, - version: 2, - updatedAt: new Date().toISOString(), - }; - - expect(result).toEqual(expectedCatalogEntry); - }); - }); - - describe("writeCatalogEntry", async () => { - it("should throw error if previous entry exists", async () => { - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: generateId(), - descriptorId: generateId(), - }); - const catalogEntry: PlatformStatesCatalogEntry = { - PK: primaryKey, - state: itemState.inactive, - descriptorAudience: "pagopa.it", - descriptorVoucherLifespan: 100, - version: 1, - updatedAt: new Date().toISOString(), - }; - await writeCatalogEntry(catalogEntry, dynamoDBClient); - expect( - writeCatalogEntry(catalogEntry, dynamoDBClient) - ).rejects.toThrowError(ConditionalCheckFailedException); - }); - - it("should write if previous entry doesn't exist", async () => { - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: generateId(), - descriptorId: generateId(), - }); - const catalogStateEntry: PlatformStatesCatalogEntry = { - PK: primaryKey, - state: itemState.inactive, - descriptorVoucherLifespan: 100, - descriptorAudience: "pagopa.it", - version: 1, - updatedAt: new Date().toISOString(), - }; - expect( - await readCatalogEntry(primaryKey, dynamoDBClient) - ).toBeUndefined(); - await writeCatalogEntry(catalogStateEntry, dynamoDBClient); - const retrievedCatalogEntry = await readCatalogEntry( - primaryKey, - dynamoDBClient - ); - - expect(retrievedCatalogEntry).toEqual(catalogStateEntry); - }); - }); - - describe("readCatalogEntry", async () => { - it("should return undefined if entry doesn't exist", async () => { - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: generateId(), - descriptorId: generateId(), - }); - const catalogEntry = await readCatalogEntry(primaryKey, dynamoDBClient); - expect(catalogEntry).toBeUndefined(); - }); - - it("should return entry if it exists", async () => { - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: generateId(), - descriptorId: generateId(), - }); - const previousCatalogStateEntry: PlatformStatesCatalogEntry = { - PK: primaryKey, - state: itemState.inactive, - descriptorAudience: "pagopa.it", - descriptorVoucherLifespan: 100, - version: 1, - updatedAt: new Date().toISOString(), - }; - await writeCatalogEntry(previousCatalogStateEntry, dynamoDBClient); - const retrievedCatalogEntry = await readCatalogEntry( - primaryKey, - dynamoDBClient - ); - - expect(retrievedCatalogEntry).toEqual(previousCatalogStateEntry); - }); - }); - - describe("deleteCatalogEntry", async () => { - it("should not throw error if previous entry doesn't exist", async () => { - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: generateId(), - descriptorId: generateId(), - }); - expect( - deleteCatalogEntry(primaryKey, dynamoDBClient) - ).resolves.not.toThrowError(); - }); - - it("should delete the entry if it exists", async () => { - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: generateId(), - descriptorId: generateId(), - }); - const previousCatalogStateEntry: PlatformStatesCatalogEntry = { - PK: primaryKey, - state: itemState.inactive, - descriptorAudience: "pagopa.it", - descriptorVoucherLifespan: 100, - version: 1, - updatedAt: new Date().toISOString(), - }; - await writeCatalogEntry(previousCatalogStateEntry, dynamoDBClient); - await deleteCatalogEntry(primaryKey, dynamoDBClient); - const retrievedCatalogEntry = await readCatalogEntry( - primaryKey, - dynamoDBClient - ); - expect(retrievedCatalogEntry).toBeUndefined(); - }); - }); - - describe("descriptorStateToClientState", async () => { - it.each([descriptorState.published, descriptorState.deprecated])( - "should convert %s state to active", - async (s) => { - expect(descriptorStateToItemState(s)).toBe(itemState.active); - } - ); - - it.each([ - descriptorState.archived, - descriptorState.draft, - descriptorState.suspended, - ])("should convert %s state to inactive", async (s) => { - expect(descriptorStateToItemState(s)).toBe(itemState.inactive); - }); - }); - - // token-generation-states - describe("writeTokenStateEntry", async () => { - it("should throw error if previous entry exists", async () => { - const tokenStateEntryPK = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: generateId(), - descriptorId: generateId(), - }); - const tokenStateEntry: TokenGenerationStatesClientPurposeEntry = { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK), - descriptorState: itemState.inactive, - descriptorAudience: "pagopa.it", - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(tokenStateEntry, dynamoDBClient); - expect( - writeTokenStateEntry(tokenStateEntry, dynamoDBClient) - ).rejects.toThrowError(ConditionalCheckFailedException); - }); - - it("should write if previous entry doesn't exist", async () => { - const tokenStateEntryPK = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: generateId(), - descriptorId: generateId(), - }); - const previousTokenStateEntries = - await readTokenStateEntriesByEserviceIdAndDescriptorId( - eserviceId_descriptorId, - dynamoDBClient - ); - expect(previousTokenStateEntries).toEqual([]); - const tokenStateEntry: TokenGenerationStatesClientPurposeEntry = { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK), - descriptorState: itemState.inactive, - descriptorAudience: "pagopa.it", - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(tokenStateEntry, dynamoDBClient); - const retrievedTokenStateEntries = - await readTokenStateEntriesByEserviceIdAndDescriptorId( - eserviceId_descriptorId, - dynamoDBClient - ); - - expect(retrievedTokenStateEntries).toEqual([tokenStateEntry]); - }); - }); - - describe("readTokenStateEntriesByEserviceIdAndDescriptorId", async () => { - it("should return empty array if entries do not exist", async () => { - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: generateId(), - descriptorId: generateId(), - }); - const result = await readTokenStateEntriesByEserviceIdAndDescriptorId( - eserviceId_descriptorId, - dynamoDBClient - ); - expect(result).toEqual([]); - }); - - it("should return entries if they exist (no need for pagination)", async () => { - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: generateId(), - descriptorId: generateId(), - }); - const tokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), - descriptorState: itemState.inactive, - descriptorAudience: "pagopa.it", - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(tokenStateEntry1, dynamoDBClient); - - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const tokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), - descriptorState: itemState.inactive, - descriptorAudience: "pagopa.it", - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(tokenStateEntry2, dynamoDBClient); - - const tokenEntries = - await readTokenStateEntriesByEserviceIdAndDescriptorId( - eserviceId_descriptorId, - dynamoDBClient - ); - - expect(tokenEntries).toEqual( - expect.arrayContaining([tokenStateEntry1, tokenStateEntry2]) - ); - }); - - it("should return entries if they exist (with pagination)", async () => { - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: generateId(), - descriptorId: generateId(), - }); - - const tokenEntriesLength = 2000; - - const writtenEntries = []; - // eslint-disable-next-line functional/no-let - for (let i = 0; i < tokenEntriesLength; i++) { - const tokenStateEntryPK = makeTokenGenerationStatesClientKidPurposePK( - { - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - } - ); - const tokenStateEntry: TokenGenerationStatesClientPurposeEntry = { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK), - descriptorState: itemState.inactive, - descriptorAudience: "pagopa.it", - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(tokenStateEntry, dynamoDBClient); - // eslint-disable-next-line functional/immutable-data - writtenEntries.push(tokenStateEntry); - } - vi.spyOn(dynamoDBClient, "send"); - const tokenEntries = - await readTokenStateEntriesByEserviceIdAndDescriptorId( - eserviceId_descriptorId, - dynamoDBClient - ); - - expect(dynamoDBClient.send).toHaveBeenCalledTimes(2); - expect(tokenEntries).toHaveLength(tokenEntriesLength); - expect(tokenEntries).toEqual(expect.arrayContaining(writtenEntries)); - }); - }); - - describe("updateDescriptorStateInTokenGenerationStatesTable", async () => { - it("should do nothing if previous entry doesn't exist", async () => { - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: generateId(), - descriptorId: generateId(), - }); - const tokenStateEntries = await readAllTokenStateItems(dynamoDBClient); - expect(tokenStateEntries).toEqual([]); - expect( - updateDescriptorStateInTokenGenerationStatesTable( - eserviceId_descriptorId, - descriptorState.archived, - dynamoDBClient - ) - ).resolves.not.toThrowError(); - const tokenStateEntriesAfterUpdate = await readAllTokenStateItems( - dynamoDBClient - ); - expect(tokenStateEntriesAfterUpdate).toEqual([]); - }); - - it("should update state if previous entries exist", async () => { - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: generateId(), - descriptorId: generateId(), - }); - const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), - descriptorState: itemState.inactive, - descriptorAudience: "pagopa.it", - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), - descriptorState: itemState.inactive, - descriptorAudience: "pagopa.it", - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); - await updateDescriptorStateInTokenGenerationStatesTable( - eserviceId_descriptorId, - descriptorState.published, - dynamoDBClient - ); - const retrievedTokenStateEntries = - await readTokenStateEntriesByEserviceIdAndDescriptorId( - eserviceId_descriptorId, - dynamoDBClient - ); - const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...previousTokenStateEntry1, - descriptorState: itemState.active, - updatedAt: new Date().toISOString(), - }; - const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...previousTokenStateEntry2, - descriptorState: itemState.active, - updatedAt: new Date().toISOString(), - }; - - expect(retrievedTokenStateEntries).toHaveLength(2); - expect(retrievedTokenStateEntries).toEqual( - expect.arrayContaining([ - expectedTokenStateEntry2, - expectedTokenStateEntry1, - ]) - ); - }); - }); - }); - describe("Events V2", async () => { describe("EServiceDescriptorActivated", () => { it("no operation if the entry already exists: incoming has version 1; previous entry has version 2", async () => { From ddd4bdc52a7b9083415d0334269a9baa244e6aed Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 11:01:37 +0200 Subject: [PATCH 210/241] Minor fix --- .../test/catalogPlatformstateWriter.integration.test.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 59fcd735c6..157026aea3 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -52,11 +52,7 @@ describe("integration tests", async () => { const dynamoDBClient = new DynamoDBClient({ credentials: { accessKeyId: "key", secretAccessKey: "secret" }, region: "eu-central-1", - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - endpoint: `http://${config.tokenGenerationReadModelDbHost}:${ - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - config.tokenGenerationReadModelDbPort - }`, + endpoint: `http://${config.tokenGenerationReadModelDbHost}:${config.tokenGenerationReadModelDbPort}`, }); beforeEach(async () => { await buildDynamoDBTables(dynamoDBClient); From 8e583885ebc12af27973b9708cb3ae78e1787767 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 11:03:13 +0200 Subject: [PATCH 211/241] Fix test structure --- ...logPlatformstateWriter.integration.test.ts | 1653 ++++++++--------- 1 file changed, 816 insertions(+), 837 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 157026aea3..8d22654fa5 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -45,7 +45,7 @@ import { import { handleMessageV2 } from "../src/consumerServiceV2.js"; import { config, sleep, writeTokenStateEntry } from "./utils.js"; -describe("integration tests", async () => { +describe("integration tests V2 events", async () => { if (!config) { fail(); } @@ -69,378 +69,827 @@ describe("integration tests", async () => { vi.useRealTimers(); }); - describe("Events V2", async () => { - describe("EServiceDescriptorActivated", () => { - it("no operation if the entry already exists: incoming has version 1; previous entry has version 2", async () => { - const publishedDescriptor: Descriptor = { - ...getMockDescriptor(), - audience: ["pagopa.it"], - interface: getMockDocument(), - state: descriptorState.published, - publishedAt: new Date(), - }; - const eservice: EService = { - ...getMockEService(), - descriptors: [publishedDescriptor], + describe("EServiceDescriptorActivated", () => { + it("no operation if the entry already exists: incoming has version 1; previous entry has version 2", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.published, + publishedAt: new Date(), + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [publishedDescriptor], + }; + const payload: EServiceDescriptorActivatedV2 = { + eservice: toEServiceV2(eservice), + descriptorId: publishedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 1, + type: "EServiceDescriptorActivated", + event_version: 2, + data: payload, + log_date: new Date(), + }; + const catalogEntryPrimaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: catalogEntryPrimaryKey, + state: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, + version: 2, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); + + // token-generation-states + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; - const payload: EServiceDescriptorActivatedV2 = { - eservice: toEServiceV2(eservice), - descriptorId: publishedDescriptor.id, + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: eservice.id, - version: 1, - type: "EServiceDescriptorActivated", - event_version: 2, - data: payload, - log_date: new Date(), + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + + await sleep(1000, mockDate); + await handleMessageV2(message, dynamoDBClient); + + // platform-states + const retrievedCatalogEntry = await readCatalogEntry( + catalogEntryPrimaryKey, + dynamoDBClient + ); + + expect(retrievedCatalogEntry).toEqual(previousStateEntry); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + + expect(retrievedTokenStateEntries).toHaveLength(2); + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + previousTokenStateEntry1, + previousTokenStateEntry2, + ]) + ); + }); + it("entry has to be updated: incoming has version 3; previous entry has version 2", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.published, + publishedAt: new Date(), + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [publishedDescriptor], + }; + + const payload: EServiceDescriptorActivatedV2 = { + eservice: toEServiceV2(eservice), + descriptorId: publishedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 2, + type: "EServiceDescriptorActivated", + event_version: 2, + data: payload, + log_date: new Date(), + }; + const catalogEntryPrimaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: catalogEntryPrimaryKey, + state: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, + version: 1, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); + + // token-generation-states + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; - const catalogEntryPrimaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, - }); - const previousStateEntry: PlatformStatesCatalogEntry = { - PK: catalogEntryPrimaryKey, - state: itemState.inactive, + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, descriptorAudience: publishedDescriptor.audience[0], - descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, - version: 2, + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + + await sleep(1000, mockDate); + await handleMessageV2(message, dynamoDBClient); + + // platform-states + const retrievedCatalogEntry = await readCatalogEntry( + catalogEntryPrimaryKey, + dynamoDBClient + ); + const expectedCatalogEntry: PlatformStatesCatalogEntry = { + ...previousStateEntry, + state: itemState.active, + version: 2, + updatedAt: new Date().toISOString(), + }; + expect(retrievedCatalogEntry).toEqual(expectedCatalogEntry); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: itemState.active, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: itemState.active, updatedAt: new Date().toISOString(), }; - await writeCatalogEntry(previousStateEntry, dynamoDBClient); - - // token-generation-states - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, - }); - const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), - descriptorState: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), - descriptorState: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); - - await sleep(1000, mockDate); - await handleMessageV2(message, dynamoDBClient); - - // platform-states - const retrievedCatalogEntry = await readCatalogEntry( - catalogEntryPrimaryKey, + + // TODO: this works, but arrayContaining must have the exact objects + // expect.arrayContaining([expectedTokenStateEntry2, expectedTokenStateEntry2]) also passes the test + expect(retrievedTokenStateEntries).toHaveLength(2); + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry2, + expectedTokenStateEntry1, + ]) + ); + }); + it("should not throw error if entry doesn't exist", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.published, + publishedAt: new Date(), + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [publishedDescriptor], + }; + + const payload: EServiceDescriptorActivatedV2 = { + eservice: toEServiceV2(eservice), + descriptorId: publishedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 2, + type: "EServiceDescriptorActivated", + event_version: 2, + data: payload, + log_date: new Date(), + }; + const catalogEntryPrimaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + + // token-generation-states + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + + await sleep(1000, mockDate); + await handleMessageV2(message, dynamoDBClient); + + // platform-states + const retrievedCatalogEntry = await readCatalogEntry( + catalogEntryPrimaryKey, + dynamoDBClient + ); + expect(retrievedCatalogEntry).toBeUndefined(); + }); + }); + + it("EServiceDescriptorArchived", async () => { + const archivedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.archived, + publishedAt: new Date(), + archivedAt: new Date(), + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [archivedDescriptor], + }; + + const payload: EServiceDescriptorArchivedV2 = { + eservice: toEServiceV2(eservice), + descriptorId: archivedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 2, + type: "EServiceDescriptorArchived", + event_version: 2, + data: payload, + log_date: new Date(), + }; + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: archivedDescriptor.id, + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.inactive, + descriptorAudience: archivedDescriptor.audience[0], + descriptorVoucherLifespan: archivedDescriptor.voucherLifespan, + version: 1, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); + + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: archivedDescriptor.id, + }); + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.active, + descriptorAudience: archivedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.active, + descriptorAudience: archivedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + await sleep(1000, mockDate); + + await handleMessageV2(message, dynamoDBClient); + + const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + expect(retrievedEntry).toBeUndefined(); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { + ...previousTokenStateEntry1, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { + ...previousTokenStateEntry2, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry1, + expectedTokenStateEntry2, + ]) + ); + }); + + describe("EServiceDescriptorPublished (the eservice has 1 descriptor)", () => { + it("no previous entry", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.published, + publishedAt: new Date(), + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [publishedDescriptor], + }; + + const payload: EServiceDescriptorPublishedV2 = { + eservice: toEServiceV2(eservice), + descriptorId: publishedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 2, + type: "EServiceDescriptorPublished", + event_version: 2, + data: payload, + log_date: new Date(), + }; + + // TO DO token-generation-states? If the descriptor was draft, there were no entries in token-generation-states + + await handleMessageV2(message, dynamoDBClient); + + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + const expectedEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, + version: 2, + updatedAt: new Date().toISOString(), + }; + expect(retrievedEntry).toEqual(expectedEntry); + }); + + // TODO: add test with incoming version 1 and previous entry version 1? + it("no operation if the entry already exists. Incoming has version 1; previous entry has version 2", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.published, + publishedAt: new Date(), + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [publishedDescriptor], + }; + + const payload: EServiceDescriptorPublishedV2 = { + eservice: toEServiceV2(eservice), + descriptorId: publishedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 1, + type: "EServiceDescriptorPublished", + event_version: 2, + data: payload, + log_date: new Date(), + }; + + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, + version: 2, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); + await handleMessageV2(message, dynamoDBClient); + + const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + expect(retrievedEntry).toEqual(previousStateEntry); + }); + it("entry has to be updated: incoming has version 3; previous entry has version 2", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.published, + publishedAt: new Date(), + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [publishedDescriptor], + }; + + const payload: EServiceDescriptorArchivedV2 = { + eservice: toEServiceV2(eservice), + descriptorId: publishedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 3, + type: "EServiceDescriptorPublished", + event_version: 2, + data: payload, + log_date: new Date(), + }; + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, + version: 2, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + await sleep(1000, mockDate); + + await handleMessageV2(message, dynamoDBClient); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, dynamoDBClient ); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: itemState.active, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: itemState.active, + updatedAt: new Date().toISOString(), + }; + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry2, + expectedTokenStateEntry1, + ]) + ); + }); + }); + + describe("EServiceDescriptorPublished (the previous descriptor becomes archived)", () => { + // these tests start with the basic flow for the current descriptor (simple write operation). Then, additional checks are added + it("entry has to be deleted", async () => { + const archivedDescriptor: Descriptor = { + ...getMockDescriptor(), + state: descriptorState.archived, + audience: ["pagopa.it"], + interface: getMockDocument(), + version: "1", + publishedAt: new Date(), + archivedAt: new Date(), + }; + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + archivedAt: new Date(), + state: descriptorState.published, + audience: ["pagopa.it"], + interface: getMockDocument(), + version: "2", + }; + + const eservice: EService = { + ...getMockEService(), + descriptors: [archivedDescriptor, publishedDescriptor], + }; + const payload: EServiceDescriptorPublishedV2 = { + eservice: toEServiceV2(eservice), + descriptorId: publishedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 2, + type: "EServiceDescriptorPublished", + event_version: 2, + data: payload, + log_date: new Date(), + }; + + await handleMessageV2(message, dynamoDBClient); - expect(retrievedCatalogEntry).toEqual(previousStateEntry); - - // token-generation-states - const retrievedTokenStateEntries = - await readTokenStateEntriesByEserviceIdAndDescriptorId( - eserviceId_descriptorId, - dynamoDBClient - ); - - expect(retrievedTokenStateEntries).toHaveLength(2); - expect(retrievedTokenStateEntries).toEqual( - expect.arrayContaining([ - previousTokenStateEntry1, - previousTokenStateEntry2, - ]) - ); + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: archivedDescriptor.id, }); - it("entry has to be updated: incoming has version 3; previous entry has version 2", async () => { - const publishedDescriptor: Descriptor = { - ...getMockDescriptor(), - audience: ["pagopa.it"], - interface: getMockDocument(), - state: descriptorState.published, - publishedAt: new Date(), - }; - const eservice: EService = { - ...getMockEService(), - descriptors: [publishedDescriptor], - }; + const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + expect(retrievedEntry).toBeUndefined(); - const payload: EServiceDescriptorActivatedV2 = { - eservice: toEServiceV2(eservice), - descriptorId: publishedDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: eservice.id, - version: 2, - type: "EServiceDescriptorActivated", - event_version: 2, - data: payload, - log_date: new Date(), - }; - const catalogEntryPrimaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, - }); - const previousStateEntry: PlatformStatesCatalogEntry = { - PK: catalogEntryPrimaryKey, - state: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, - version: 1, - updatedAt: new Date().toISOString(), - }; - await writeCatalogEntry(previousStateEntry, dynamoDBClient); - - // token-generation-states - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, - }); - const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), - descriptorState: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), - descriptorState: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); - - await sleep(1000, mockDate); - await handleMessageV2(message, dynamoDBClient); - - // platform-states - const retrievedCatalogEntry = await readCatalogEntry( - catalogEntryPrimaryKey, - dynamoDBClient - ); - const expectedCatalogEntry: PlatformStatesCatalogEntry = { - ...previousStateEntry, - state: itemState.active, - version: 2, - updatedAt: new Date().toISOString(), - }; - expect(retrievedCatalogEntry).toEqual(expectedCatalogEntry); - - // token-generation-states - const retrievedTokenStateEntries = - await readTokenStateEntriesByEserviceIdAndDescriptorId( - eserviceId_descriptorId, - dynamoDBClient - ); - const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...previousTokenStateEntry1, - descriptorState: itemState.active, - updatedAt: new Date().toISOString(), - }; - const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...previousTokenStateEntry2, - descriptorState: itemState.active, - updatedAt: new Date().toISOString(), - }; - - // TODO: this works, but arrayContaining must have the exact objects - // expect.arrayContaining([expectedTokenStateEntry2, expectedTokenStateEntry2]) also passes the test - expect(retrievedTokenStateEntries).toHaveLength(2); - expect(retrievedTokenStateEntries).toEqual( - expect.arrayContaining([ - expectedTokenStateEntry2, - expectedTokenStateEntry1, - ]) - ); + // TO DO token-generation-states + }); + }); + + describe("EServiceDescriptorSuspended", () => { + it("no operation if the entry already exists: incoming has version 1; previous entry has version 2", async () => { + const suspendedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.suspended, + publishedAt: new Date(), + suspendedAt: new Date(), + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [suspendedDescriptor], + }; + + const payload: EServiceDescriptorSuspendedV2 = { + eservice: toEServiceV2(eservice), + descriptorId: suspendedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 1, + type: "EServiceDescriptorSuspended", + event_version: 2, + data: payload, + log_date: new Date(), + }; + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: suspendedDescriptor.id, }); - it("should not throw error if entry doesn't exist", async () => { - const publishedDescriptor: Descriptor = { - ...getMockDescriptor(), - audience: ["pagopa.it"], - interface: getMockDocument(), - state: descriptorState.published, - publishedAt: new Date(), - }; - const eservice: EService = { - ...getMockEService(), - descriptors: [publishedDescriptor], - }; + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.active, + descriptorAudience: suspendedDescriptor.audience[0], + descriptorVoucherLifespan: suspendedDescriptor.voucherLifespan, + version: 2, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); - const payload: EServiceDescriptorActivatedV2 = { - eservice: toEServiceV2(eservice), - descriptorId: publishedDescriptor.id, + // token-generation-states + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: suspendedDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.active, + descriptorAudience: suspendedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: eservice.id, - version: 2, - type: "EServiceDescriptorActivated", - event_version: 2, - data: payload, - log_date: new Date(), + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.active, + descriptorAudience: suspendedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; - const catalogEntryPrimaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, - }); - - // token-generation-states - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, - }); - const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), - descriptorState: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), - descriptorState: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); - - await sleep(1000, mockDate); - await handleMessageV2(message, dynamoDBClient); - - // platform-states - const retrievedCatalogEntry = await readCatalogEntry( - catalogEntryPrimaryKey, + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + + await handleMessageV2(message, dynamoDBClient); + + const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + + expect(retrievedEntry).toEqual(previousStateEntry); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, dynamoDBClient ); - expect(retrievedCatalogEntry).toBeUndefined(); - }); - }); - it("EServiceDescriptorArchived", async () => { - const archivedDescriptor: Descriptor = { + expect(retrievedTokenStateEntries).toHaveLength(2); + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + previousTokenStateEntry2, + previousTokenStateEntry1, + ]) + ); + }); + it("entry has to be updated: incoming has version 3; previous entry has version 2", async () => { + const suspendedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], interface: getMockDocument(), - state: descriptorState.archived, + state: descriptorState.suspended, publishedAt: new Date(), - archivedAt: new Date(), + suspendedAt: new Date(), }; + const eservice: EService = { ...getMockEService(), - descriptors: [archivedDescriptor], + descriptors: [suspendedDescriptor], }; - - const payload: EServiceDescriptorArchivedV2 = { + const payload: EServiceDescriptorSuspendedV2 = { eservice: toEServiceV2(eservice), - descriptorId: archivedDescriptor.id, + descriptorId: suspendedDescriptor.id, }; const message: EServiceEventEnvelope = { sequence_num: 1, stream_id: eservice.id, version: 2, - type: "EServiceDescriptorArchived", + type: "EServiceDescriptorSuspended", event_version: 2, data: payload, log_date: new Date(), }; const primaryKey = makePlatformStatesEServiceDescriptorPK({ eserviceId: eservice.id, - descriptorId: archivedDescriptor.id, + descriptorId: suspendedDescriptor.id, }); const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, - state: itemState.inactive, - descriptorAudience: archivedDescriptor.audience[0], - descriptorVoucherLifespan: archivedDescriptor.voucherLifespan, + state: itemState.active, + descriptorAudience: suspendedDescriptor.audience[0], + descriptorVoucherLifespan: suspendedDescriptor.voucherLifespan, version: 1, updatedAt: new Date().toISOString(), }; await writeCatalogEntry(previousStateEntry, dynamoDBClient); - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: eservice.id, - descriptorId: archivedDescriptor.id, - }); + // token-generation-states const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ clientId: generateId(), kid: `kid ${Math.random()}`, purposeId: generateId(), }); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: suspendedDescriptor.id, }); const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), descriptorState: itemState.active, - descriptorAudience: archivedDescriptor.audience[0], + descriptorAudience: suspendedDescriptor.audience[0], GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), descriptorState: itemState.active, - descriptorAudience: archivedDescriptor.audience[0], + descriptorAudience: suspendedDescriptor.audience[0], GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); - await sleep(1000, mockDate); await handleMessageV2(message, dynamoDBClient); const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); - expect(retrievedEntry).toBeUndefined(); + const expectedEntry: PlatformStatesCatalogEntry = { + ...previousStateEntry, + state: itemState.inactive, + version: 2, + updatedAt: new Date().toISOString(), + }; + expect(retrievedEntry).toEqual(expectedEntry); // token-generation-states const retrievedTokenStateEntries = @@ -460,559 +909,89 @@ describe("integration tests", async () => { descriptorState: itemState.inactive, updatedAt: new Date().toISOString(), }; + + expect(retrievedTokenStateEntries).toHaveLength(2); expect(retrievedTokenStateEntries).toEqual( expect.arrayContaining([ - expectedTokenStateEntry1, expectedTokenStateEntry2, + expectedTokenStateEntry1, ]) ); }); + it("should not throw error if entry doesn't exist", async () => { + const suspendedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.suspended, + publishedAt: new Date(), + suspendedAt: new Date(), + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [suspendedDescriptor], + }; - describe("EServiceDescriptorPublished (the eservice has 1 descriptor)", () => { - it("no previous entry", async () => { - const publishedDescriptor: Descriptor = { - ...getMockDescriptor(), - audience: ["pagopa.it"], - interface: getMockDocument(), - state: descriptorState.published, - publishedAt: new Date(), - }; - const eservice: EService = { - ...getMockEService(), - descriptors: [publishedDescriptor], - }; - - const payload: EServiceDescriptorPublishedV2 = { - eservice: toEServiceV2(eservice), - descriptorId: publishedDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: eservice.id, - version: 2, - type: "EServiceDescriptorPublished", - event_version: 2, - data: payload, - log_date: new Date(), - }; - - // TO DO token-generation-states? If the descriptor was draft, there were no entries in token-generation-states - - await handleMessageV2(message, dynamoDBClient); - - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, - }); - const retrievedEntry = await readCatalogEntry( - primaryKey, - dynamoDBClient - ); - const expectedEntry: PlatformStatesCatalogEntry = { - PK: primaryKey, - state: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], - descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, - version: 2, - updatedAt: new Date().toISOString(), - }; - expect(retrievedEntry).toEqual(expectedEntry); - }); - - // TODO: add test with incoming version 1 and previous entry version 1? - it("no operation if the entry already exists. Incoming has version 1; previous entry has version 2", async () => { - const publishedDescriptor: Descriptor = { - ...getMockDescriptor(), - audience: ["pagopa.it"], - interface: getMockDocument(), - state: descriptorState.published, - publishedAt: new Date(), - }; - const eservice: EService = { - ...getMockEService(), - descriptors: [publishedDescriptor], - }; - - const payload: EServiceDescriptorPublishedV2 = { - eservice: toEServiceV2(eservice), - descriptorId: publishedDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: eservice.id, - version: 1, - type: "EServiceDescriptorPublished", - event_version: 2, - data: payload, - log_date: new Date(), - }; - - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, - }); - const previousStateEntry: PlatformStatesCatalogEntry = { - PK: primaryKey, - state: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], - descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, - version: 2, - updatedAt: new Date().toISOString(), - }; - await writeCatalogEntry(previousStateEntry, dynamoDBClient); - await handleMessageV2(message, dynamoDBClient); - - const retrievedEntry = await readCatalogEntry( - primaryKey, - dynamoDBClient - ); - expect(retrievedEntry).toEqual(previousStateEntry); + const payload: EServiceDescriptorActivatedV2 = { + eservice: toEServiceV2(eservice), + descriptorId: suspendedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 2, + type: "EServiceDescriptorActivated", + event_version: 2, + data: payload, + log_date: new Date(), + }; + const catalogEntryPrimaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: suspendedDescriptor.id, }); - it("entry has to be updated: incoming has version 3; previous entry has version 2", async () => { - const publishedDescriptor: Descriptor = { - ...getMockDescriptor(), - audience: ["pagopa.it"], - interface: getMockDocument(), - state: descriptorState.published, - publishedAt: new Date(), - }; - const eservice: EService = { - ...getMockEService(), - descriptors: [publishedDescriptor], - }; - const payload: EServiceDescriptorArchivedV2 = { - eservice: toEServiceV2(eservice), - descriptorId: publishedDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: eservice.id, - version: 3, - type: "EServiceDescriptorPublished", - event_version: 2, - data: payload, - log_date: new Date(), - }; - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, - }); - const previousStateEntry: PlatformStatesCatalogEntry = { - PK: primaryKey, - state: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, - version: 2, - updatedAt: new Date().toISOString(), - }; - await writeCatalogEntry(previousStateEntry, dynamoDBClient); - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, - }); - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), - descriptorState: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - - const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), - descriptorState: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); - await sleep(1000, mockDate); - - await handleMessageV2(message, dynamoDBClient); - - // token-generation-states - const retrievedTokenStateEntries = - await readTokenStateEntriesByEserviceIdAndDescriptorId( - eserviceId_descriptorId, - dynamoDBClient - ); - const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...previousTokenStateEntry1, - descriptorState: itemState.active, - updatedAt: new Date().toISOString(), - }; - const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...previousTokenStateEntry2, - descriptorState: itemState.active, - updatedAt: new Date().toISOString(), - }; - expect(retrievedTokenStateEntries).toEqual( - expect.arrayContaining([ - expectedTokenStateEntry2, - expectedTokenStateEntry1, - ]) - ); + // token-generation-states + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), }); - }); - - describe("EServiceDescriptorPublished (the previous descriptor becomes archived)", () => { - // these tests start with the basic flow for the current descriptor (simple write operation). Then, additional checks are added - it("entry has to be deleted", async () => { - const archivedDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.archived, - audience: ["pagopa.it"], - interface: getMockDocument(), - version: "1", - publishedAt: new Date(), - archivedAt: new Date(), - }; - const publishedDescriptor: Descriptor = { - ...getMockDescriptor(), - archivedAt: new Date(), - state: descriptorState.published, - audience: ["pagopa.it"], - interface: getMockDocument(), - version: "2", - }; - - const eservice: EService = { - ...getMockEService(), - descriptors: [archivedDescriptor, publishedDescriptor], - }; - const payload: EServiceDescriptorPublishedV2 = { - eservice: toEServiceV2(eservice), - descriptorId: publishedDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: eservice.id, - version: 2, - type: "EServiceDescriptorPublished", - event_version: 2, - data: payload, - log_date: new Date(), - }; - - await handleMessageV2(message, dynamoDBClient); - - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: archivedDescriptor.id, - }); - const retrievedEntry = await readCatalogEntry( - primaryKey, - dynamoDBClient - ); - expect(retrievedEntry).toBeUndefined(); - - // TO DO token-generation-states + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: suspendedDescriptor.id, }); - }); - - describe("EServiceDescriptorSuspended", () => { - it("no operation if the entry already exists: incoming has version 1; previous entry has version 2", async () => { - const suspendedDescriptor: Descriptor = { - ...getMockDescriptor(), - audience: ["pagopa.it"], - interface: getMockDocument(), - state: descriptorState.suspended, - publishedAt: new Date(), - suspendedAt: new Date(), - }; - const eservice: EService = { - ...getMockEService(), - descriptors: [suspendedDescriptor], - }; - - const payload: EServiceDescriptorSuspendedV2 = { - eservice: toEServiceV2(eservice), - descriptorId: suspendedDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: eservice.id, - version: 1, - type: "EServiceDescriptorSuspended", - event_version: 2, - data: payload, - log_date: new Date(), - }; - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: suspendedDescriptor.id, - }); - const previousStateEntry: PlatformStatesCatalogEntry = { - PK: primaryKey, - state: itemState.active, + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, descriptorAudience: suspendedDescriptor.audience[0], - descriptorVoucherLifespan: suspendedDescriptor.voucherLifespan, - version: 2, - updatedAt: new Date().toISOString(), + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; - await writeCatalogEntry(previousStateEntry, dynamoDBClient); - - // token-generation-states - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: eservice.id, - descriptorId: suspendedDescriptor.id, - }); - const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), - descriptorState: itemState.active, - descriptorAudience: suspendedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), - descriptorState: itemState.active, - descriptorAudience: suspendedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); - - await handleMessageV2(message, dynamoDBClient); - - const retrievedEntry = await readCatalogEntry( - primaryKey, - dynamoDBClient - ); + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - expect(retrievedEntry).toEqual(previousStateEntry); - - // token-generation-states - const retrievedTokenStateEntries = - await readTokenStateEntriesByEserviceIdAndDescriptorId( - eserviceId_descriptorId, - dynamoDBClient - ); - - expect(retrievedTokenStateEntries).toHaveLength(2); - expect(retrievedTokenStateEntries).toEqual( - expect.arrayContaining([ - previousTokenStateEntry2, - previousTokenStateEntry1, - ]) - ); + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), }); - it("entry has to be updated: incoming has version 3; previous entry has version 2", async () => { - const suspendedDescriptor: Descriptor = { - ...getMockDescriptor(), - audience: ["pagopa.it"], - interface: getMockDocument(), - state: descriptorState.suspended, - publishedAt: new Date(), - suspendedAt: new Date(), - }; - - const eservice: EService = { - ...getMockEService(), - descriptors: [suspendedDescriptor], - }; - const payload: EServiceDescriptorSuspendedV2 = { - eservice: toEServiceV2(eservice), - descriptorId: suspendedDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: eservice.id, - version: 2, - type: "EServiceDescriptorSuspended", - event_version: 2, - data: payload, - log_date: new Date(), - }; - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: suspendedDescriptor.id, - }); - const previousStateEntry: PlatformStatesCatalogEntry = { - PK: primaryKey, - state: itemState.active, + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, descriptorAudience: suspendedDescriptor.audience[0], - descriptorVoucherLifespan: suspendedDescriptor.voucherLifespan, - version: 1, - updatedAt: new Date().toISOString(), - }; - await writeCatalogEntry(previousStateEntry, dynamoDBClient); - - // token-generation-states - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: eservice.id, - descriptorId: suspendedDescriptor.id, - }); - const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), - descriptorState: itemState.active, - descriptorAudience: suspendedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), - descriptorState: itemState.active, - descriptorAudience: suspendedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); - - await handleMessageV2(message, dynamoDBClient); - - const retrievedEntry = await readCatalogEntry( - primaryKey, - dynamoDBClient - ); - const expectedEntry: PlatformStatesCatalogEntry = { - ...previousStateEntry, - state: itemState.inactive, - version: 2, - updatedAt: new Date().toISOString(), - }; - expect(retrievedEntry).toEqual(expectedEntry); - - // token-generation-states - const retrievedTokenStateEntries = - await readTokenStateEntriesByEserviceIdAndDescriptorId( - eserviceId_descriptorId, - dynamoDBClient - ); - const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...previousTokenStateEntry1, - descriptorState: itemState.inactive, - updatedAt: new Date().toISOString(), - }; - const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...previousTokenStateEntry2, - descriptorState: itemState.inactive, - updatedAt: new Date().toISOString(), - }; - - expect(retrievedTokenStateEntries).toHaveLength(2); - expect(retrievedTokenStateEntries).toEqual( - expect.arrayContaining([ - expectedTokenStateEntry2, - expectedTokenStateEntry1, - ]) - ); - }); - it("should not throw error if entry doesn't exist", async () => { - const suspendedDescriptor: Descriptor = { - ...getMockDescriptor(), - audience: ["pagopa.it"], - interface: getMockDocument(), - state: descriptorState.suspended, - publishedAt: new Date(), - suspendedAt: new Date(), - }; - const eservice: EService = { - ...getMockEService(), - descriptors: [suspendedDescriptor], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); - const payload: EServiceDescriptorActivatedV2 = { - eservice: toEServiceV2(eservice), - descriptorId: suspendedDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: eservice.id, - version: 2, - type: "EServiceDescriptorActivated", - event_version: 2, - data: payload, - log_date: new Date(), - }; - const catalogEntryPrimaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: suspendedDescriptor.id, - }); - - // token-generation-states - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: eservice.id, - descriptorId: suspendedDescriptor.id, - }); - const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), - descriptorState: itemState.inactive, - descriptorAudience: suspendedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), - descriptorState: itemState.inactive, - descriptorAudience: suspendedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); - - await sleep(1000, mockDate); - await handleMessageV2(message, dynamoDBClient); - - // platform-states - const retrievedCatalogEntry = await readCatalogEntry( - catalogEntryPrimaryKey, - dynamoDBClient - ); - expect(retrievedCatalogEntry).toBeUndefined(); - }); + await sleep(1000, mockDate); + await handleMessageV2(message, dynamoDBClient); + + // platform-states + const retrievedCatalogEntry = await readCatalogEntry( + catalogEntryPrimaryKey, + dynamoDBClient + ); + expect(retrievedCatalogEntry).toBeUndefined(); }); }); }); From ab04510ad4cdd8b09ad6f50a2b349944b2ba178c Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 11:46:13 +0200 Subject: [PATCH 212/241] Fix tests --- ...logPlatformstateWriter.integration.test.ts | 343 ++++++++++++------ 1 file changed, 234 insertions(+), 109 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index 8d22654fa5..d66a71c7dd 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -70,7 +70,7 @@ describe("integration tests V2 events", async () => { }); describe("EServiceDescriptorActivated", () => { - it("no operation if the entry already exists: incoming has version 1; previous entry has version 2", async () => { + it("should do no operation if the entry already exists: incoming has version 1; previous entry has version 2", async () => { const publishedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], @@ -163,12 +163,12 @@ describe("integration tests V2 events", async () => { expect(retrievedTokenStateEntries).toHaveLength(2); expect(retrievedTokenStateEntries).toEqual( expect.arrayContaining([ - previousTokenStateEntry1, previousTokenStateEntry2, + previousTokenStateEntry1, ]) ); }); - it("entry has to be updated: incoming has version 3; previous entry has version 2", async () => { + it("should update the entry: incoming has version 3; previous entry has version 2", async () => { const publishedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], @@ -188,7 +188,7 @@ describe("integration tests V2 events", async () => { const message: EServiceEventEnvelope = { sequence_num: 1, stream_id: eservice.id, - version: 2, + version: 3, type: "EServiceDescriptorActivated", event_version: 2, data: payload, @@ -203,7 +203,7 @@ describe("integration tests V2 events", async () => { state: itemState.inactive, descriptorAudience: publishedDescriptor.audience[0], descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, - version: 1, + version: 2, updatedAt: new Date().toISOString(), }; await writeCatalogEntry(previousStateEntry, dynamoDBClient); @@ -252,7 +252,7 @@ describe("integration tests V2 events", async () => { const expectedCatalogEntry: PlatformStatesCatalogEntry = { ...previousStateEntry, state: itemState.active, - version: 2, + version: 3, updatedAt: new Date().toISOString(), }; expect(retrievedCatalogEntry).toEqual(expectedCatalogEntry); @@ -351,7 +351,9 @@ describe("integration tests V2 events", async () => { await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); await sleep(1000, mockDate); - await handleMessageV2(message, dynamoDBClient); + expect( + handleMessageV2(message, dynamoDBClient) + ).resolves.not.toThrowError(); // platform-states const retrievedCatalogEntry = await readCatalogEntry( @@ -359,108 +361,129 @@ describe("integration tests V2 events", async () => { dynamoDBClient ); expect(retrievedCatalogEntry).toBeUndefined(); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + + expect(retrievedTokenStateEntries).toHaveLength(2); + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + previousTokenStateEntry2, + previousTokenStateEntry1, + ]) + ); }); }); - it("EServiceDescriptorArchived", async () => { - const archivedDescriptor: Descriptor = { - ...getMockDescriptor(), - audience: ["pagopa.it"], - interface: getMockDocument(), - state: descriptorState.archived, - publishedAt: new Date(), - archivedAt: new Date(), - }; - const eservice: EService = { - ...getMockEService(), - descriptors: [archivedDescriptor], - }; - - const payload: EServiceDescriptorArchivedV2 = { - eservice: toEServiceV2(eservice), - descriptorId: archivedDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: eservice.id, - version: 2, - type: "EServiceDescriptorArchived", - event_version: 2, - data: payload, - log_date: new Date(), - }; - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: archivedDescriptor.id, - }); - const previousStateEntry: PlatformStatesCatalogEntry = { - PK: primaryKey, - state: itemState.inactive, - descriptorAudience: archivedDescriptor.audience[0], - descriptorVoucherLifespan: archivedDescriptor.voucherLifespan, - version: 1, - updatedAt: new Date().toISOString(), - }; - await writeCatalogEntry(previousStateEntry, dynamoDBClient); - - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: eservice.id, - descriptorId: archivedDescriptor.id, - }); - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), - descriptorState: itemState.active, - descriptorAudience: archivedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - - const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), - descriptorState: itemState.active, - descriptorAudience: archivedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); - await sleep(1000, mockDate); - - await handleMessageV2(message, dynamoDBClient); - - const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); - expect(retrievedEntry).toBeUndefined(); - - // token-generation-states - const retrievedTokenStateEntries = - await readTokenStateEntriesByEserviceIdAndDescriptorId( - eserviceId_descriptorId, - dynamoDBClient + describe("EServiceDescriptorArchived", () => { + it("should delete the entry from platform states", async () => { + const archivedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.archived, + publishedAt: new Date(), + archivedAt: new Date(), + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [archivedDescriptor], + }; + + const payload: EServiceDescriptorArchivedV2 = { + eservice: toEServiceV2(eservice), + descriptorId: archivedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 2, + type: "EServiceDescriptorArchived", + event_version: 2, + data: payload, + log_date: new Date(), + }; + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: archivedDescriptor.id, + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.inactive, + descriptorAudience: archivedDescriptor.audience[0], + descriptorVoucherLifespan: archivedDescriptor.voucherLifespan, + version: 1, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); + + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: archivedDescriptor.id, + }); + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.active, + descriptorAudience: archivedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.active, + descriptorAudience: archivedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + await sleep(1000, mockDate); + + await handleMessageV2(message, dynamoDBClient); + + const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + expect(retrievedEntry).toBeUndefined(); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry1, + expectedTokenStateEntry2, + ]) ); - const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { - ...previousTokenStateEntry1, - descriptorState: itemState.inactive, - updatedAt: new Date().toISOString(), - }; - const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { - ...previousTokenStateEntry2, - descriptorState: itemState.inactive, - updatedAt: new Date().toISOString(), - }; - expect(retrievedTokenStateEntries).toEqual( - expect.arrayContaining([ - expectedTokenStateEntry1, - expectedTokenStateEntry2, - ]) - ); + }); }); describe("EServiceDescriptorPublished (the eservice has 1 descriptor)", () => { @@ -491,7 +514,38 @@ describe("integration tests V2 events", async () => { log_date: new Date(), }; - // TO DO token-generation-states? If the descriptor was draft, there were no entries in token-generation-states + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); await handleMessageV2(message, dynamoDBClient); @@ -509,10 +563,34 @@ describe("integration tests V2 events", async () => { updatedAt: new Date().toISOString(), }; expect(retrievedEntry).toEqual(expectedEntry); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: itemState.active, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: itemState.active, + updatedAt: new Date().toISOString(), + }; + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry1, + expectedTokenStateEntry2, + ]) + ); }); - // TODO: add test with incoming version 1 and previous entry version 1? - it("no operation if the entry already exists. Incoming has version 1; previous entry has version 2", async () => { + it("should do no operation if the entry already exists. Incoming has version 1; previous entry has version 2", async () => { const publishedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], @@ -552,12 +630,59 @@ describe("integration tests V2 events", async () => { updatedAt: new Date().toISOString(), }; await writeCatalogEntry(previousStateEntry, dynamoDBClient); + + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); await handleMessageV2(message, dynamoDBClient); const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); expect(retrievedEntry).toEqual(previousStateEntry); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + previousTokenStateEntry1, + previousTokenStateEntry2, + ]) + ); }); - it("entry has to be updated: incoming has version 3; previous entry has version 2", async () => { + it("should update the entry: incoming has version 3; previous entry has version 2", async () => { const publishedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], From 3e3769ffdf50ea4dbc04cc44b9a32ac3c84cd0a4 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 11:54:46 +0200 Subject: [PATCH 213/241] Fix --- packages/catalog-platformstate-writer/src/consumerServiceV2.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index 9cc512d3bb..81958ad381 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -124,7 +124,7 @@ export async function handleMessageV2( }); await updateDescriptorStateInTokenGenerationStatesTable( eserviceId_descriptorId_previous, - descriptor.state, + previousDescriptor.state, dynamoDBClient ); } From daddb4ba5cf4d68940016fdeb971f0fb8c9ac534 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 11:57:27 +0200 Subject: [PATCH 214/241] Improve tests --- ...logPlatformstateWriter.integration.test.ts | 727 ++++++++++-------- 1 file changed, 405 insertions(+), 322 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts index d66a71c7dd..83c9487abe 100644 --- a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts +++ b/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts @@ -486,357 +486,423 @@ describe("integration tests V2 events", async () => { }); }); - describe("EServiceDescriptorPublished (the eservice has 1 descriptor)", () => { - it("no previous entry", async () => { - const publishedDescriptor: Descriptor = { - ...getMockDescriptor(), - audience: ["pagopa.it"], - interface: getMockDocument(), - state: descriptorState.published, - publishedAt: new Date(), - }; - const eservice: EService = { - ...getMockEService(), - descriptors: [publishedDescriptor], - }; - - const payload: EServiceDescriptorPublishedV2 = { - eservice: toEServiceV2(eservice), - descriptorId: publishedDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: eservice.id, - version: 2, - type: "EServiceDescriptorPublished", - event_version: 2, - data: payload, - log_date: new Date(), - }; - - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, - }); - - const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), - descriptorState: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + describe("EServiceDescriptorPublished", () => { + describe("the eservice has 1 descriptor", () => { + it("should add the entry if if doesn't exists", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.published, + publishedAt: new Date(), }; - await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - - const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), - descriptorState: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + const eservice: EService = { + ...getMockEService(), + descriptors: [publishedDescriptor], }; - await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); - - await handleMessageV2(message, dynamoDBClient); - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, - }); - const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); - const expectedEntry: PlatformStatesCatalogEntry = { - PK: primaryKey, - state: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], - descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, - version: 2, - updatedAt: new Date().toISOString(), - }; - expect(retrievedEntry).toEqual(expectedEntry); + const payload: EServiceDescriptorPublishedV2 = { + eservice: toEServiceV2(eservice), + descriptorId: publishedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 2, + type: "EServiceDescriptorPublished", + event_version: 2, + data: payload, + log_date: new Date(), + }; - // token-generation-states - const retrievedTokenStateEntries = - await readTokenStateEntriesByEserviceIdAndDescriptorId( - eserviceId_descriptorId, + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + + await handleMessageV2(message, dynamoDBClient); + + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const retrievedEntry = await readCatalogEntry( + primaryKey, dynamoDBClient ); - const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...previousTokenStateEntry1, - descriptorState: itemState.active, - updatedAt: new Date().toISOString(), - }; - const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...previousTokenStateEntry2, - descriptorState: itemState.active, + const expectedEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, + version: 2, updatedAt: new Date().toISOString(), }; - expect(retrievedTokenStateEntries).toEqual( - expect.arrayContaining([ - expectedTokenStateEntry1, - expectedTokenStateEntry2, - ]) - ); - }); - - it("should do no operation if the entry already exists. Incoming has version 1; previous entry has version 2", async () => { - const publishedDescriptor: Descriptor = { - ...getMockDescriptor(), - audience: ["pagopa.it"], - interface: getMockDocument(), - state: descriptorState.published, - publishedAt: new Date(), - }; - const eservice: EService = { - ...getMockEService(), - descriptors: [publishedDescriptor], - }; - - const payload: EServiceDescriptorPublishedV2 = { - eservice: toEServiceV2(eservice), - descriptorId: publishedDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: eservice.id, - version: 1, - type: "EServiceDescriptorPublished", - event_version: 2, - data: payload, - log_date: new Date(), - }; - - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, + expect(retrievedEntry).toEqual(expectedEntry); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: itemState.active, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: itemState.active, + updatedAt: new Date().toISOString(), + }; + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry1, + expectedTokenStateEntry2, + ]) + ); }); - const previousStateEntry: PlatformStatesCatalogEntry = { - PK: primaryKey, - state: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], - descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, - version: 2, - updatedAt: new Date().toISOString(), - }; - await writeCatalogEntry(previousStateEntry, dynamoDBClient); - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, - }); + it("should do no operation if the entry already exists. Incoming has version 1; previous entry has version 2", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.published, + publishedAt: new Date(), + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [publishedDescriptor], + }; - const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), - descriptorState: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + const payload: EServiceDescriptorPublishedV2 = { + eservice: toEServiceV2(eservice), + descriptorId: publishedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 1, + type: "EServiceDescriptorPublished", + event_version: 2, + data: payload, + log_date: new Date(), }; - await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), - descriptorState: itemState.inactive, + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.active, descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, + version: 2, + updatedAt: new Date().toISOString(), }; - await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); - await handleMessageV2(message, dynamoDBClient); - - const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); - expect(retrievedEntry).toEqual(previousStateEntry); - - // token-generation-states - const retrievedTokenStateEntries = - await readTokenStateEntriesByEserviceIdAndDescriptorId( - eserviceId_descriptorId, + await writeCatalogEntry(previousStateEntry, dynamoDBClient); + + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + await handleMessageV2(message, dynamoDBClient); + + const retrievedEntry = await readCatalogEntry( + primaryKey, dynamoDBClient ); - - expect(retrievedTokenStateEntries).toEqual( - expect.arrayContaining([ - previousTokenStateEntry1, - previousTokenStateEntry2, - ]) - ); - }); - it("should update the entry: incoming has version 3; previous entry has version 2", async () => { - const publishedDescriptor: Descriptor = { - ...getMockDescriptor(), - audience: ["pagopa.it"], - interface: getMockDocument(), - state: descriptorState.published, - publishedAt: new Date(), - }; - const eservice: EService = { - ...getMockEService(), - descriptors: [publishedDescriptor], - }; - - const payload: EServiceDescriptorArchivedV2 = { - eservice: toEServiceV2(eservice), - descriptorId: publishedDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: eservice.id, - version: 3, - type: "EServiceDescriptorPublished", - event_version: 2, - data: payload, - log_date: new Date(), - }; - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, - }); - const previousStateEntry: PlatformStatesCatalogEntry = { - PK: primaryKey, - state: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, - version: 2, - updatedAt: new Date().toISOString(), - }; - await writeCatalogEntry(previousStateEntry, dynamoDBClient); - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, - }); - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), + expect(retrievedEntry).toEqual(previousStateEntry); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + previousTokenStateEntry1, + previousTokenStateEntry2, + ]) + ); }); - const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), - descriptorState: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + it("should update the entry: incoming has version 3; previous entry has version 2", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.published, + publishedAt: new Date(), }; - await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - - const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), - descriptorState: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + const eservice: EService = { + ...getMockEService(), + descriptors: [publishedDescriptor], }; - await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); - await sleep(1000, mockDate); - - await handleMessageV2(message, dynamoDBClient); - // token-generation-states - const retrievedTokenStateEntries = - await readTokenStateEntriesByEserviceIdAndDescriptorId( - eserviceId_descriptorId, - dynamoDBClient - ); - const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...previousTokenStateEntry1, - descriptorState: itemState.active, - updatedAt: new Date().toISOString(), + const payload: EServiceDescriptorArchivedV2 = { + eservice: toEServiceV2(eservice), + descriptorId: publishedDescriptor.id, }; - const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...previousTokenStateEntry2, - descriptorState: itemState.active, + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 3, + type: "EServiceDescriptorPublished", + event_version: 2, + data: payload, + log_date: new Date(), + }; + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, + version: 2, updatedAt: new Date().toISOString(), }; - expect(retrievedTokenStateEntries).toEqual( - expect.arrayContaining([ - expectedTokenStateEntry2, - expectedTokenStateEntry1, - ]) - ); + await writeCatalogEntry(previousStateEntry, dynamoDBClient); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + await sleep(1000, mockDate); + + await handleMessageV2(message, dynamoDBClient); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: itemState.active, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: itemState.active, + updatedAt: new Date().toISOString(), + }; + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry2, + expectedTokenStateEntry1, + ]) + ); + }); }); - }); - describe("EServiceDescriptorPublished (the previous descriptor becomes archived)", () => { - // these tests start with the basic flow for the current descriptor (simple write operation). Then, additional checks are added - it("entry has to be deleted", async () => { - const archivedDescriptor: Descriptor = { - ...getMockDescriptor(), - state: descriptorState.archived, - audience: ["pagopa.it"], - interface: getMockDocument(), - version: "1", - publishedAt: new Date(), - archivedAt: new Date(), - }; - const publishedDescriptor: Descriptor = { - ...getMockDescriptor(), - archivedAt: new Date(), - state: descriptorState.published, - audience: ["pagopa.it"], - interface: getMockDocument(), - version: "2", - }; - - const eservice: EService = { - ...getMockEService(), - descriptors: [archivedDescriptor, publishedDescriptor], - }; - const payload: EServiceDescriptorPublishedV2 = { - eservice: toEServiceV2(eservice), - descriptorId: publishedDescriptor.id, - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: eservice.id, - version: 2, - type: "EServiceDescriptorPublished", - event_version: 2, - data: payload, - log_date: new Date(), - }; + describe("the previous descriptor becomes archived", () => { + // these tests start with the basic flow for the current descriptor (simple write operation). Then, additional checks are added + it("should delete the entry", async () => { + const archivedDescriptor: Descriptor = { + ...getMockDescriptor(), + state: descriptorState.archived, + audience: ["pagopa.it"], + interface: getMockDocument(), + version: "1", + publishedAt: new Date(), + archivedAt: new Date(), + }; + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + archivedAt: new Date(), + state: descriptorState.published, + audience: ["pagopa.it"], + interface: getMockDocument(), + version: "2", + }; - await handleMessageV2(message, dynamoDBClient); + const eservice: EService = { + ...getMockEService(), + descriptors: [archivedDescriptor, publishedDescriptor], + }; + const payload: EServiceDescriptorPublishedV2 = { + eservice: toEServiceV2(eservice), + descriptorId: publishedDescriptor.id, + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 2, + type: "EServiceDescriptorPublished", + event_version: 2, + data: payload, + log_date: new Date(), + }; - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: archivedDescriptor.id, + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: archivedDescriptor.id, + }); + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + + await handleMessageV2(message, dynamoDBClient); + + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: archivedDescriptor.id, + }); + const retrievedEntry = await readCatalogEntry( + primaryKey, + dynamoDBClient + ); + expect(retrievedEntry).toBeUndefined(); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry2, + expectedTokenStateEntry1, + ]) + ); }); - const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); - expect(retrievedEntry).toBeUndefined(); - - // TO DO token-generation-states }); }); describe("EServiceDescriptorSuspended", () => { - it("no operation if the entry already exists: incoming has version 1; previous entry has version 2", async () => { + it("should do no operation if the entry already exists: incoming has version 1; previous entry has version 2", async () => { const suspendedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], @@ -931,7 +997,7 @@ describe("integration tests V2 events", async () => { ]) ); }); - it("entry has to be updated: incoming has version 3; previous entry has version 2", async () => { + it("should update the entry: incoming has version 3; previous entry has version 2", async () => { const suspendedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], @@ -1057,7 +1123,7 @@ describe("integration tests V2 events", async () => { descriptors: [suspendedDescriptor], }; - const payload: EServiceDescriptorActivatedV2 = { + const payload: EServiceDescriptorSuspendedV2 = { eservice: toEServiceV2(eservice), descriptorId: suspendedDescriptor.id, }; @@ -1065,7 +1131,7 @@ describe("integration tests V2 events", async () => { sequence_num: 1, stream_id: eservice.id, version: 2, - type: "EServiceDescriptorActivated", + type: "EServiceDescriptorSuspended", event_version: 2, data: payload, log_date: new Date(), @@ -1109,7 +1175,9 @@ describe("integration tests V2 events", async () => { await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); await sleep(1000, mockDate); - await handleMessageV2(message, dynamoDBClient); + expect( + handleMessageV2(message, dynamoDBClient) + ).resolves.not.toThrowError(); // platform-states const retrievedCatalogEntry = await readCatalogEntry( @@ -1117,6 +1185,21 @@ describe("integration tests V2 events", async () => { dynamoDBClient ); expect(retrievedCatalogEntry).toBeUndefined(); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + + expect(retrievedTokenStateEntries).toHaveLength(2); + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + previousTokenStateEntry1, + previousTokenStateEntry2, + ]) + ); }); }); }); From c8758278e35df593ff3810e6fc87dddac03c3beb Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 12:08:53 +0200 Subject: [PATCH 215/241] Refactor --- .../src/consumerServiceV2.ts | 39 +++++++++---------- .../catalog-platformstate-writer/src/utils.ts | 4 +- .../test/utils.test.ts | 4 +- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index 81958ad381..c5f63dc141 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -7,9 +7,9 @@ import { EServiceEventEnvelopeV2, EServiceV2, fromEServiceV2, - genericInternalError, makeGSIPKEServiceIdDescriptorId, makePlatformStatesEServiceDescriptorPK, + missingKafkaMessageDataError, PlatformStatesCatalogEntry, unsafeBrandId, } from "pagopa-interop-models"; @@ -31,7 +31,8 @@ export async function handleMessageV2( .with({ type: "EServiceDescriptorPublished" }, async (msg) => { const { eservice, descriptor } = parseEServiceAndDescriptor( msg.data.eservice, - unsafeBrandId(msg.data.descriptorId) + unsafeBrandId(msg.data.descriptorId), + message.type ); const previousDescriptor = eservice.descriptors.find( (d) => d.version === (Number(descriptor.version) - 1).toString() @@ -71,7 +72,7 @@ export async function handleMessageV2( }); await updateDescriptorStateInTokenGenerationStatesTable( eserviceId_descriptorId, - descriptor.state, + descriptorStateToItemState(descriptor.state), dynamoDBClient ); } else { @@ -93,7 +94,7 @@ export async function handleMessageV2( }); await updateDescriptorStateInTokenGenerationStatesTable( eserviceId_descriptorId, - descriptor.state, + descriptorStateToItemState(descriptor.state), dynamoDBClient ); } @@ -124,7 +125,7 @@ export async function handleMessageV2( }); await updateDescriptorStateInTokenGenerationStatesTable( eserviceId_descriptorId_previous, - previousDescriptor.state, + descriptorStateToItemState(previousDescriptor.state), dynamoDBClient ); } @@ -135,7 +136,8 @@ export async function handleMessageV2( async (msg) => { const { eservice, descriptor } = parseEServiceAndDescriptor( msg.data.eservice, - unsafeBrandId(msg.data.descriptorId) + unsafeBrandId(msg.data.descriptorId), + message.type ); const primaryKey = makePlatformStatesEServiceDescriptorPK({ eserviceId: eservice.id, @@ -160,20 +162,18 @@ export async function handleMessageV2( }); await updateDescriptorStateInTokenGenerationStatesTable( eserviceId_descriptorId, - descriptor.state, + descriptorStateToItemState(descriptor.state), dynamoDBClient ); } } ) .with({ type: "EServiceDescriptorArchived" }, async (msg) => { - const eserviceV2 = msg.data.eservice; - if (!eserviceV2) { - throw genericInternalError( - `EService not found in message data for event ${msg.type}` - ); - } - const eservice = fromEServiceV2(eserviceV2); + const { eservice, descriptor } = parseEServiceAndDescriptor( + msg.data.eservice, + unsafeBrandId(msg.data.descriptorId), + msg.type + ); const primaryKey = makePlatformStatesEServiceDescriptorPK({ eserviceId: eservice.id, @@ -189,7 +189,7 @@ export async function handleMessageV2( }); await updateDescriptorStateInTokenGenerationStatesTable( eserviceId_descriptorId, - descriptorState.archived, + descriptorStateToItemState(descriptor.state), dynamoDBClient ); }) @@ -219,19 +219,18 @@ export async function handleMessageV2( export const parseEServiceAndDescriptor = ( eserviceV2: EServiceV2 | undefined, - descriptorId: DescriptorId + descriptorId: DescriptorId, + eventType: string ): { eservice: EService; descriptor: Descriptor } => { if (!eserviceV2) { - throw genericInternalError(`EService not found in message data`); + throw missingKafkaMessageDataError("eservice", eventType); } const eservice = fromEServiceV2(eserviceV2); const descriptor = eservice.descriptors.find((d) => d.id === descriptorId); if (!descriptor) { - throw genericInternalError( - `Unable to find descriptor with id ${descriptorId}` - ); + throw missingKafkaMessageDataError("descriptor", eventType); } return { eservice, descriptor }; }; diff --git a/packages/catalog-platformstate-writer/src/utils.ts b/packages/catalog-platformstate-writer/src/utils.ts index 5ffe5a4348..a07be6ba71 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -210,7 +210,7 @@ export const readTokenStateEntriesByEserviceIdAndDescriptorId = async ( export const updateDescriptorStateInTokenGenerationStatesTable = async ( eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, - descriptorState: DescriptorState, + descriptorState: ItemState, dynamoDBClient: DynamoDBClient ): Promise => { const entriesToUpdate = @@ -229,7 +229,7 @@ export const updateDescriptorStateInTokenGenerationStatesTable = async ( }, ExpressionAttributeValues: { ":newState": { - S: descriptorStateToItemState(descriptorState), + S: descriptorState, }, ":newUpdateAt": { S: new Date().toISOString(), diff --git a/packages/catalog-platformstate-writer/test/utils.test.ts b/packages/catalog-platformstate-writer/test/utils.test.ts index e96746af6e..4713dfab10 100644 --- a/packages/catalog-platformstate-writer/test/utils.test.ts +++ b/packages/catalog-platformstate-writer/test/utils.test.ts @@ -418,7 +418,7 @@ describe("utils tests", async () => { expect( updateDescriptorStateInTokenGenerationStatesTable( eserviceId_descriptorId, - descriptorState.archived, + itemState.inactive, dynamoDBClient ) ).resolves.not.toThrowError(); @@ -462,7 +462,7 @@ describe("utils tests", async () => { await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); await updateDescriptorStateInTokenGenerationStatesTable( eserviceId_descriptorId, - descriptorState.published, + itemState.active, dynamoDBClient ); const retrievedTokenStateEntries = From 0b27fbe14c4bd1e9e8fe974f16abb834909a9b1a Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 12:20:54 +0200 Subject: [PATCH 216/241] Refactor test --- .../src/consumerServiceV1.ts | 9 +- .../test/consumerServiceV1.test.ts | 629 ++++++++++++++++++ 2 files changed, 634 insertions(+), 4 deletions(-) create mode 100644 packages/catalog-platformstate-writer/test/consumerServiceV1.test.ts diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index 919f27b078..be57ca902c 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -71,7 +71,7 @@ export async function handleMessageV1( }); await updateDescriptorStateInTokenGenerationStatesTable( eserviceId_descriptorId, - descriptor.state, + descriptorStateToItemState(descriptor.state), dynamoDBClient ); } else { @@ -79,6 +79,7 @@ export async function handleMessageV1( PK: eserviceDescriptorPK, state: descriptorStateToItemState(descriptor.state), descriptorAudience: descriptor.audience[0], + descriptorVoucherLifespan: descriptor.voucherLifespan, version: msg.version, updatedAt: new Date().toISOString(), }; @@ -92,7 +93,7 @@ export async function handleMessageV1( }); await updateDescriptorStateInTokenGenerationStatesTable( eserviceId_descriptorId, - descriptor.state, + descriptorStateToItemState(descriptor.state), dynamoDBClient ); } @@ -129,7 +130,7 @@ export async function handleMessageV1( }); await updateDescriptorStateInTokenGenerationStatesTable( eserviceId_descriptorId, - descriptor.state, + descriptorStateToItemState(descriptor.state), dynamoDBClient ); } @@ -158,7 +159,7 @@ export async function handleMessageV1( }); await updateDescriptorStateInTokenGenerationStatesTable( eserviceId_descriptorId, - descriptor.state, + descriptorStateToItemState(descriptor.state), dynamoDBClient ); }) diff --git a/packages/catalog-platformstate-writer/test/consumerServiceV1.test.ts b/packages/catalog-platformstate-writer/test/consumerServiceV1.test.ts new file mode 100644 index 0000000000..861c994d50 --- /dev/null +++ b/packages/catalog-platformstate-writer/test/consumerServiceV1.test.ts @@ -0,0 +1,629 @@ +/* eslint-disable @typescript-eslint/no-floating-promises */ +import { fail } from "assert"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, + vi, +} from "vitest"; +import { + Descriptor, + EService, + EServiceDescriptorUpdatedV1, + EServiceEventEnvelope, + PlatformStatesCatalogEntry, + TokenGenerationStatesClientPurposeEntry, + descriptorState, + generateId, + itemState, + makeGSIPKEServiceIdDescriptorId, + makePlatformStatesEServiceDescriptorPK, + makeTokenGenerationStatesClientKidPurposePK, +} from "pagopa-interop-models"; +import { + toDescriptorV1, + getMockDescriptor, + getMockEService, + getMockDocument, + getMockTokenStatesClientPurposeEntry, + buildDynamoDBTables, + deleteDynamoDBTables, +} from "pagopa-interop-commons-test"; +import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; +import { handleMessageV1 } from "../src/consumerServiceV1.js"; +import { + readCatalogEntry, + readTokenStateEntriesByEserviceIdAndDescriptorId, + writeCatalogEntry, +} from "../src/utils.js"; +import { config, sleep, writeTokenStateEntry } from "./utils.js"; + +describe("V1 events", async () => { + if (!config) { + fail(); + } + const dynamoDBClient = new DynamoDBClient({ + credentials: { accessKeyId: "key", secretAccessKey: "secret" }, + region: "eu-central-1", + endpoint: `http://${config.tokenGenerationReadModelDbHost}:${config.tokenGenerationReadModelDbPort}`, + }); + beforeEach(async () => { + await buildDynamoDBTables(dynamoDBClient); + }); + afterEach(async () => { + await deleteDynamoDBTables(dynamoDBClient); + }); + const mockDate = new Date(); + beforeAll(() => { + vi.useFakeTimers(); + vi.setSystemTime(mockDate); + }); + afterAll(() => { + vi.useRealTimers(); + }); + + describe("Events V1", async () => { + it("EServiceDescriptorUpdated (draft -> published)", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + publishedAt: new Date(), + state: descriptorState.published, + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [publishedDescriptor], + }; + + const payload: EServiceDescriptorUpdatedV1 = { + eserviceId: eservice.id, + eserviceDescriptor: toDescriptorV1(publishedDescriptor), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 2, + type: "EServiceDescriptorUpdated", + event_version: 1, + data: payload, + log_date: new Date(), + }; + await handleMessageV1(message, dynamoDBClient); + await sleep(1000, mockDate); + + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + const expectedEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, + version: 2, + updatedAt: new Date().toISOString(), + }; + expect(retrievedEntry).toEqual(expectedEntry); + }); + + it("EServiceDescriptorUpdated (suspended -> published, version of the event is newer)", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + publishedAt: new Date(), + suspendedAt: undefined, + state: descriptorState.published, + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [publishedDescriptor], + }; + + const payload: EServiceDescriptorUpdatedV1 = { + eserviceId: eservice.id, + eserviceDescriptor: toDescriptorV1(publishedDescriptor), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 2, + type: "EServiceDescriptorUpdated", + event_version: 1, + data: payload, + log_date: new Date(), + }; + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, + version: 1, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); + + // token-generation-states + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + + await handleMessageV1(message, dynamoDBClient); + await sleep(1000, mockDate); + + const retrievedCatalogEntry = await readCatalogEntry( + primaryKey, + dynamoDBClient + ); + const expectedCatalogEntry: PlatformStatesCatalogEntry = { + ...previousStateEntry, + state: itemState.active, + version: 2, + }; + expect(retrievedCatalogEntry).toEqual(expectedCatalogEntry); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: itemState.active, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: itemState.active, + updatedAt: new Date().toISOString(), + }; + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry1, + expectedTokenStateEntry2, + ]) + ); + }); + + it("EServiceDescriptorUpdated (published, no operation if version of the event is lower than existing entry)", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + state: descriptorState.published, + publishedAt: new Date(), + suspendedAt: new Date(), + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [publishedDescriptor], + }; + + const payload: EServiceDescriptorUpdatedV1 = { + eserviceId: eservice.id, + eserviceDescriptor: toDescriptorV1(publishedDescriptor), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 1, + type: "EServiceDescriptorUpdated", + event_version: 1, + data: payload, + log_date: new Date(), + }; + const catalogPrimaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousCatalogStateEntry: PlatformStatesCatalogEntry = { + PK: catalogPrimaryKey, + state: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, + version: 2, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousCatalogStateEntry, dynamoDBClient); + + // token-generation-states + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + + await handleMessageV1(message, dynamoDBClient); + await sleep(1000, mockDate); + + const retrievedCatalogEntry = await readCatalogEntry( + catalogPrimaryKey, + dynamoDBClient + ); + expect(retrievedCatalogEntry).toEqual(previousCatalogStateEntry); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + previousTokenStateEntry1, + previousTokenStateEntry2, + ]) + ); + }); + + describe("EServiceDescriptorUpdated (published -> suspended)", () => { + it("should perform the update if msg.version >= existing version", async () => { + const suspendedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + publishedAt: new Date(), + suspendedAt: new Date(), + state: descriptorState.suspended, + }; + + const eservice: EService = { + ...getMockEService(), + descriptors: [suspendedDescriptor], + }; + + const payload: EServiceDescriptorUpdatedV1 = { + eserviceId: eservice.id, + eserviceDescriptor: toDescriptorV1(suspendedDescriptor), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 2, + type: "EServiceDescriptorUpdated", + event_version: 1, + data: payload, + log_date: new Date(), + }; + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: suspendedDescriptor.id, + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.active, + descriptorAudience: suspendedDescriptor.audience[0], + descriptorVoucherLifespan: suspendedDescriptor.voucherLifespan, + version: 1, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); + + // token-generation-states + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: suspendedDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.active, + descriptorAudience: suspendedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.active, + descriptorAudience: suspendedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + await handleMessageV1(message, dynamoDBClient); + await sleep(1000, mockDate); + + const retrievedEntry = await readCatalogEntry( + primaryKey, + dynamoDBClient + ); + const expectedEntry: PlatformStatesCatalogEntry = { + ...previousStateEntry, + state: itemState.inactive, + version: 2, + }; + expect(retrievedEntry).toEqual(expectedEntry); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry1, + expectedTokenStateEntry2, + ]) + ); + }); + + it("should do nothing if msg.version < existing version", async () => { + const suspendedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + publishedAt: new Date(), + suspendedAt: new Date(), + state: descriptorState.suspended, + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [suspendedDescriptor], + }; + + const payload: EServiceDescriptorUpdatedV1 = { + eserviceId: eservice.id, + eserviceDescriptor: toDescriptorV1(suspendedDescriptor), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 2, + type: "EServiceDescriptorUpdated", + event_version: 1, + data: payload, + log_date: new Date(), + }; + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: suspendedDescriptor.id, + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.active, + descriptorAudience: suspendedDescriptor.audience[0], + descriptorVoucherLifespan: suspendedDescriptor.voucherLifespan, + version: 3, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); + await handleMessageV1(message, dynamoDBClient); + await sleep(1000, mockDate); + + const retrievedEntry = await readCatalogEntry( + primaryKey, + dynamoDBClient + ); + expect(retrievedEntry).toEqual(previousStateEntry); + }); + + it("should throw error if previous entry doesn't exist", async () => { + const suspendedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + publishedAt: new Date(), + suspendedAt: new Date(), + state: descriptorState.suspended, + }; + + const eservice: EService = { + ...getMockEService(), + descriptors: [suspendedDescriptor], + }; + + const payload: EServiceDescriptorUpdatedV1 = { + eserviceId: eservice.id, + eserviceDescriptor: toDescriptorV1(suspendedDescriptor), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 2, + type: "EServiceDescriptorUpdated", + event_version: 1, + data: payload, + log_date: new Date(), + }; + expect(handleMessageV1(message, dynamoDBClient)).rejects.toThrowError(); + }); + }); + + it("EServiceDescriptorUpdated (published -> archived)", async () => { + const archivedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + publishedAt: new Date(), + archivedAt: new Date(), + state: descriptorState.archived, + }; + + const eservice: EService = { + ...getMockEService(), + descriptors: [archivedDescriptor], + }; + const payload: EServiceDescriptorUpdatedV1 = { + eserviceId: eservice.id, + eserviceDescriptor: toDescriptorV1(archivedDescriptor), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 2, + type: "EServiceDescriptorUpdated", + event_version: 1, + data: payload, + log_date: new Date(), + }; + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: archivedDescriptor.id, + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.inactive, + descriptorAudience: archivedDescriptor.audience[0], + descriptorVoucherLifespan: archivedDescriptor.voucherLifespan, + version: 1, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); + + // token-generation-states + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: archivedDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.active, + descriptorAudience: archivedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.active, + descriptorAudience: archivedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + + await handleMessageV1(message, dynamoDBClient); + await sleep(1000, mockDate); + + const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + expect(retrievedEntry).toBeUndefined(); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry1, + expectedTokenStateEntry2, + ]) + ); + }); + }); +}); From d8a6f375b4b054c16156116113606ca61aeada21 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Tue, 24 Sep 2024 12:22:24 +0200 Subject: [PATCH 217/241] Rename test file for events V2 --- ...mstateWriter.integration.test.ts => consumerServiceV2.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/catalog-platformstate-writer/test/{catalogPlatformstateWriter.integration.test.ts => consumerServiceV2.test.ts} (100%) diff --git a/packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts b/packages/catalog-platformstate-writer/test/consumerServiceV2.test.ts similarity index 100% rename from packages/catalog-platformstate-writer/test/catalogPlatformstateWriter.integration.test.ts rename to packages/catalog-platformstate-writer/test/consumerServiceV2.test.ts From 4c4d753bfecc87d9a2dc503a5a8e1312f35deed8 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Tue, 24 Sep 2024 12:36:21 +0200 Subject: [PATCH 218/241] Fix typo --- .../catalog-platformstate-writer/test/consumerServiceV2.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/catalog-platformstate-writer/test/consumerServiceV2.test.ts b/packages/catalog-platformstate-writer/test/consumerServiceV2.test.ts index 83c9487abe..5d2618284d 100644 --- a/packages/catalog-platformstate-writer/test/consumerServiceV2.test.ts +++ b/packages/catalog-platformstate-writer/test/consumerServiceV2.test.ts @@ -488,7 +488,7 @@ describe("integration tests V2 events", async () => { describe("EServiceDescriptorPublished", () => { describe("the eservice has 1 descriptor", () => { - it("should add the entry if if doesn't exists", async () => { + it("should add the entry if it doesn't exist", async () => { const publishedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], From 609b392bc78aaa068ba5209942eba07e2eb93ab4 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 12:53:53 +0200 Subject: [PATCH 219/241] Fix --- .../src/consumerServiceV1.ts | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index be57ca902c..bb96a0d810 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -1,12 +1,14 @@ import { match } from "ts-pattern"; import { + Descriptor, descriptorState, + EServiceDescriptorV1, EServiceEventEnvelopeV1, EServiceId, fromDescriptorV1, - genericInternalError, makeGSIPKEServiceIdDescriptorId, makePlatformStatesEServiceDescriptorPK, + missingKafkaMessageDataError, PlatformStatesCatalogEntry, unsafeBrandId, } from "pagopa-interop-models"; @@ -27,13 +29,8 @@ export async function handleMessageV1( await match(message) .with({ type: "EServiceDescriptorUpdated" }, async (msg) => { const eserviceId = unsafeBrandId(msg.data.eserviceId); - const descriptorV1 = msg.data.eserviceDescriptor; - if (!descriptorV1) { - throw genericInternalError( - `EServiceDescriptor not found in message data for event ${msg.type}` - ); - } - const descriptor = fromDescriptorV1(descriptorV1); + const descriptor = parseDescriptor(msg.data.eserviceDescriptor, msg.type); + const eserviceDescriptorPK = makePlatformStatesEServiceDescriptorPK({ eserviceId, descriptorId: descriptor.id, @@ -104,15 +101,10 @@ export async function handleMessageV1( dynamoDBClient ); - if (!existingCatalogEntry) { - throw genericInternalError( - `EServiceDescriptor not found in catalog for event ${msg.type}` - ); - } else if ( - existingCatalogEntry && + if ( + !existingCatalogEntry || existingCatalogEntry.version > msg.version ) { - // Stops processing if the message is older than the catalog entry return Promise.resolve(); } else { // platform-states @@ -137,13 +129,10 @@ export async function handleMessageV1( }) .with(descriptorState.archived, async () => { const eserviceId = unsafeBrandId(msg.data.eserviceId); - const descriptorV1 = msg.data.eserviceDescriptor; - if (!descriptorV1) { - throw genericInternalError( - `EServiceDescriptor not found in message data for event ${msg.type}` - ); - } - const descriptor = fromDescriptorV1(descriptorV1); + const descriptor = parseDescriptor( + msg.data.eserviceDescriptor, + msg.type + ); // platform-states const primaryKey = makePlatformStatesEServiceDescriptorPK({ @@ -186,3 +175,13 @@ export async function handleMessageV1( ) .exhaustive(); } + +export const parseDescriptor = ( + descriptorV1: EServiceDescriptorV1 | undefined, + eventType: string +): Descriptor => { + if (!descriptorV1) { + throw missingKafkaMessageDataError("descriptor", eventType); + } + return fromDescriptorV1(descriptorV1); +}; From 0f51a6750f820c05e06785a92a655ec107faf0c9 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 12:54:00 +0200 Subject: [PATCH 220/241] Update tests --- .../test/consumerServiceV1.test.ts | 744 ++++++++++-------- 1 file changed, 435 insertions(+), 309 deletions(-) diff --git a/packages/catalog-platformstate-writer/test/consumerServiceV1.test.ts b/packages/catalog-platformstate-writer/test/consumerServiceV1.test.ts index 861c994d50..f2fb058438 100644 --- a/packages/catalog-platformstate-writer/test/consumerServiceV1.test.ts +++ b/packages/catalog-platformstate-writer/test/consumerServiceV1.test.ts @@ -67,281 +67,23 @@ describe("V1 events", async () => { }); describe("Events V1", async () => { - it("EServiceDescriptorUpdated (draft -> published)", async () => { - const publishedDescriptor: Descriptor = { - ...getMockDescriptor(), - audience: ["pagopa.it"], - interface: getMockDocument(), - publishedAt: new Date(), - state: descriptorState.published, - }; - const eservice: EService = { - ...getMockEService(), - descriptors: [publishedDescriptor], - }; - - const payload: EServiceDescriptorUpdatedV1 = { - eserviceId: eservice.id, - eserviceDescriptor: toDescriptorV1(publishedDescriptor), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: eservice.id, - version: 2, - type: "EServiceDescriptorUpdated", - event_version: 1, - data: payload, - log_date: new Date(), - }; - await handleMessageV1(message, dynamoDBClient); - await sleep(1000, mockDate); - - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, - }); - const retrievedEntry = await readCatalogEntry(primaryKey, dynamoDBClient); - const expectedEntry: PlatformStatesCatalogEntry = { - PK: primaryKey, - state: itemState.active, - descriptorAudience: publishedDescriptor.audience[0], - descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, - version: 2, - updatedAt: new Date().toISOString(), - }; - expect(retrievedEntry).toEqual(expectedEntry); - }); - - it("EServiceDescriptorUpdated (suspended -> published, version of the event is newer)", async () => { - const publishedDescriptor: Descriptor = { - ...getMockDescriptor(), - audience: ["pagopa.it"], - interface: getMockDocument(), - publishedAt: new Date(), - suspendedAt: undefined, - state: descriptorState.published, - }; - const eservice: EService = { - ...getMockEService(), - descriptors: [publishedDescriptor], - }; - - const payload: EServiceDescriptorUpdatedV1 = { - eserviceId: eservice.id, - eserviceDescriptor: toDescriptorV1(publishedDescriptor), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: eservice.id, - version: 2, - type: "EServiceDescriptorUpdated", - event_version: 1, - data: payload, - log_date: new Date(), - }; - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, - }); - const previousStateEntry: PlatformStatesCatalogEntry = { - PK: primaryKey, - state: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, - version: 1, - updatedAt: new Date().toISOString(), - }; - await writeCatalogEntry(previousStateEntry, dynamoDBClient); - - // token-generation-states - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, - }); - const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), - descriptorState: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), - descriptorState: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); - - await handleMessageV1(message, dynamoDBClient); - await sleep(1000, mockDate); - - const retrievedCatalogEntry = await readCatalogEntry( - primaryKey, - dynamoDBClient - ); - const expectedCatalogEntry: PlatformStatesCatalogEntry = { - ...previousStateEntry, - state: itemState.active, - version: 2, - }; - expect(retrievedCatalogEntry).toEqual(expectedCatalogEntry); - - // token-generation-states - const retrievedTokenStateEntries = - await readTokenStateEntriesByEserviceIdAndDescriptorId( - eserviceId_descriptorId, - dynamoDBClient - ); - const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...previousTokenStateEntry1, - descriptorState: itemState.active, - updatedAt: new Date().toISOString(), - }; - const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...previousTokenStateEntry2, - descriptorState: itemState.active, - updatedAt: new Date().toISOString(), - }; - expect(retrievedTokenStateEntries).toEqual( - expect.arrayContaining([ - expectedTokenStateEntry1, - expectedTokenStateEntry2, - ]) - ); - }); - - it("EServiceDescriptorUpdated (published, no operation if version of the event is lower than existing entry)", async () => { - const publishedDescriptor: Descriptor = { - ...getMockDescriptor(), - audience: ["pagopa.it"], - interface: getMockDocument(), - state: descriptorState.published, - publishedAt: new Date(), - suspendedAt: new Date(), - }; - const eservice: EService = { - ...getMockEService(), - descriptors: [publishedDescriptor], - }; - - const payload: EServiceDescriptorUpdatedV1 = { - eserviceId: eservice.id, - eserviceDescriptor: toDescriptorV1(publishedDescriptor), - }; - const message: EServiceEventEnvelope = { - sequence_num: 1, - stream_id: eservice.id, - version: 1, - type: "EServiceDescriptorUpdated", - event_version: 1, - data: payload, - log_date: new Date(), - }; - const catalogPrimaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, - }); - const previousCatalogStateEntry: PlatformStatesCatalogEntry = { - PK: catalogPrimaryKey, - state: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, - version: 2, - updatedAt: new Date().toISOString(), - }; - await writeCatalogEntry(previousCatalogStateEntry, dynamoDBClient); - - // token-generation-states - const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: eservice.id, - descriptorId: publishedDescriptor.id, - }); - const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), - descriptorState: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); - - const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ - clientId: generateId(), - kid: `kid ${Math.random()}`, - purposeId: generateId(), - }); - const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = - { - ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), - descriptorState: itemState.inactive, - descriptorAudience: publishedDescriptor.audience[0], - GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, - }; - await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); - - await handleMessageV1(message, dynamoDBClient); - await sleep(1000, mockDate); - - const retrievedCatalogEntry = await readCatalogEntry( - catalogPrimaryKey, - dynamoDBClient - ); - expect(retrievedCatalogEntry).toEqual(previousCatalogStateEntry); - - // token-generation-states - const retrievedTokenStateEntries = - await readTokenStateEntriesByEserviceIdAndDescriptorId( - eserviceId_descriptorId, - dynamoDBClient - ); - expect(retrievedTokenStateEntries).toEqual( - expect.arrayContaining([ - previousTokenStateEntry1, - previousTokenStateEntry2, - ]) - ); - }); - - describe("EServiceDescriptorUpdated (published -> suspended)", () => { - it("should perform the update if msg.version >= existing version", async () => { - const suspendedDescriptor: Descriptor = { + describe("EServiceDescriptorUpdated", () => { + it("(draft -> published) should add the entry if it doesn't exist", async () => { + const publishedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], interface: getMockDocument(), publishedAt: new Date(), - suspendedAt: new Date(), - state: descriptorState.suspended, + state: descriptorState.published, }; - const eservice: EService = { ...getMockEService(), - descriptors: [suspendedDescriptor], + descriptors: [publishedDescriptor], }; const payload: EServiceDescriptorUpdatedV1 = { eserviceId: eservice.id, - eserviceDescriptor: toDescriptorV1(suspendedDescriptor), + eserviceDescriptor: toDescriptorV1(publishedDescriptor), }; const message: EServiceEventEnvelope = { sequence_num: 1, @@ -352,19 +94,6 @@ describe("V1 events", async () => { data: payload, log_date: new Date(), }; - const primaryKey = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: suspendedDescriptor.id, - }); - const previousStateEntry: PlatformStatesCatalogEntry = { - PK: primaryKey, - state: itemState.active, - descriptorAudience: suspendedDescriptor.audience[0], - descriptorVoucherLifespan: suspendedDescriptor.voucherLifespan, - version: 1, - updatedAt: new Date().toISOString(), - }; - await writeCatalogEntry(previousStateEntry, dynamoDBClient); // token-generation-states const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ @@ -374,13 +103,13 @@ describe("V1 events", async () => { }); const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ eserviceId: eservice.id, - descriptorId: suspendedDescriptor.id, + descriptorId: publishedDescriptor.id, }); const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), - descriptorState: itemState.active, - descriptorAudience: suspendedDescriptor.audience[0], + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); @@ -393,22 +122,30 @@ describe("V1 events", async () => { const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), - descriptorState: itemState.active, - descriptorAudience: suspendedDescriptor.audience[0], + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, }; await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + await handleMessageV1(message, dynamoDBClient); await sleep(1000, mockDate); + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); const retrievedEntry = await readCatalogEntry( primaryKey, dynamoDBClient ); const expectedEntry: PlatformStatesCatalogEntry = { - ...previousStateEntry, - state: itemState.inactive, + PK: primaryKey, + state: itemState.active, + descriptorAudience: publishedDescriptor.audience[0], + descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, version: 2, + updatedAt: new Date().toISOString(), }; expect(retrievedEntry).toEqual(expectedEntry); @@ -421,13 +158,13 @@ describe("V1 events", async () => { const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = { ...previousTokenStateEntry1, - descriptorState: itemState.inactive, + descriptorState: itemState.active, updatedAt: new Date().toISOString(), }; const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = { ...previousTokenStateEntry2, - descriptorState: itemState.inactive, + descriptorState: itemState.active, updatedAt: new Date().toISOString(), }; expect(retrievedTokenStateEntries).toEqual( @@ -437,24 +174,23 @@ describe("V1 events", async () => { ]) ); }); - - it("should do nothing if msg.version < existing version", async () => { - const suspendedDescriptor: Descriptor = { + it("(suspended -> published) should update the entry if msg.version >= existing version", async () => { + const publishedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], interface: getMockDocument(), publishedAt: new Date(), - suspendedAt: new Date(), - state: descriptorState.suspended, + suspendedAt: undefined, + state: descriptorState.published, }; const eservice: EService = { ...getMockEService(), - descriptors: [suspendedDescriptor], + descriptors: [publishedDescriptor], }; const payload: EServiceDescriptorUpdatedV1 = { eserviceId: eservice.id, - eserviceDescriptor: toDescriptorV1(suspendedDescriptor), + eserviceDescriptor: toDescriptorV1(publishedDescriptor), }; const message: EServiceEventEnvelope = { sequence_num: 1, @@ -467,60 +203,450 @@ describe("V1 events", async () => { }; const primaryKey = makePlatformStatesEServiceDescriptorPK({ eserviceId: eservice.id, - descriptorId: suspendedDescriptor.id, + descriptorId: publishedDescriptor.id, }); const previousStateEntry: PlatformStatesCatalogEntry = { PK: primaryKey, - state: itemState.active, - descriptorAudience: suspendedDescriptor.audience[0], - descriptorVoucherLifespan: suspendedDescriptor.voucherLifespan, - version: 3, + state: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, + version: 1, updatedAt: new Date().toISOString(), }; await writeCatalogEntry(previousStateEntry, dynamoDBClient); + + // token-generation-states + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + await handleMessageV1(message, dynamoDBClient); await sleep(1000, mockDate); - const retrievedEntry = await readCatalogEntry( + const retrievedCatalogEntry = await readCatalogEntry( primaryKey, dynamoDBClient ); - expect(retrievedEntry).toEqual(previousStateEntry); + const expectedCatalogEntry: PlatformStatesCatalogEntry = { + ...previousStateEntry, + state: itemState.active, + version: 2, + }; + expect(retrievedCatalogEntry).toEqual(expectedCatalogEntry); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: itemState.active, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: itemState.active, + updatedAt: new Date().toISOString(), + }; + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry1, + expectedTokenStateEntry2, + ]) + ); }); - it("should throw error if previous entry doesn't exist", async () => { - const suspendedDescriptor: Descriptor = { + it("(published) should do no operation if msg.version < existing version", async () => { + const publishedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], interface: getMockDocument(), + state: descriptorState.published, publishedAt: new Date(), suspendedAt: new Date(), - state: descriptorState.suspended, }; - const eservice: EService = { ...getMockEService(), - descriptors: [suspendedDescriptor], + descriptors: [publishedDescriptor], }; const payload: EServiceDescriptorUpdatedV1 = { eserviceId: eservice.id, - eserviceDescriptor: toDescriptorV1(suspendedDescriptor), + eserviceDescriptor: toDescriptorV1(publishedDescriptor), }; const message: EServiceEventEnvelope = { sequence_num: 1, stream_id: eservice.id, - version: 2, + version: 1, type: "EServiceDescriptorUpdated", event_version: 1, data: payload, log_date: new Date(), }; - expect(handleMessageV1(message, dynamoDBClient)).rejects.toThrowError(); + const catalogPrimaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousCatalogStateEntry: PlatformStatesCatalogEntry = { + PK: catalogPrimaryKey, + state: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, + version: 2, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousCatalogStateEntry, dynamoDBClient); + + // token-generation-states + const tokenStateEntryPK1 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: publishedDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + + await handleMessageV1(message, dynamoDBClient); + await sleep(1000, mockDate); + + const retrievedCatalogEntry = await readCatalogEntry( + catalogPrimaryKey, + dynamoDBClient + ); + expect(retrievedCatalogEntry).toEqual(previousCatalogStateEntry); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + previousTokenStateEntry1, + previousTokenStateEntry2, + ]) + ); + }); + + describe("(published -> suspended)", () => { + it("should update the entry if msg.version >= existing version", async () => { + const suspendedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + publishedAt: new Date(), + suspendedAt: new Date(), + state: descriptorState.suspended, + }; + + const eservice: EService = { + ...getMockEService(), + descriptors: [suspendedDescriptor], + }; + + const payload: EServiceDescriptorUpdatedV1 = { + eserviceId: eservice.id, + eserviceDescriptor: toDescriptorV1(suspendedDescriptor), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 2, + type: "EServiceDescriptorUpdated", + event_version: 1, + data: payload, + log_date: new Date(), + }; + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: suspendedDescriptor.id, + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.active, + descriptorAudience: suspendedDescriptor.audience[0], + descriptorVoucherLifespan: suspendedDescriptor.voucherLifespan, + version: 1, + updatedAt: new Date().toISOString(), + }; + await writeCatalogEntry(previousStateEntry, dynamoDBClient); + + // token-generation-states + const tokenStateEntryPK1 = + makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: suspendedDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.active, + descriptorAudience: suspendedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const tokenStateEntryPK2 = + makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.active, + descriptorAudience: suspendedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + await handleMessageV1(message, dynamoDBClient); + await sleep(1000, mockDate); + + const retrievedEntry = await readCatalogEntry( + primaryKey, + dynamoDBClient + ); + const expectedEntry: PlatformStatesCatalogEntry = { + ...previousStateEntry, + state: itemState.inactive, + version: 2, + }; + expect(retrievedEntry).toEqual(expectedEntry); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + const expectedTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry1, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + const expectedTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...previousTokenStateEntry2, + descriptorState: itemState.inactive, + updatedAt: new Date().toISOString(), + }; + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + expectedTokenStateEntry1, + expectedTokenStateEntry2, + ]) + ); + }); + + it("should do no operation if msg.version < existing version", async () => { + const suspendedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + publishedAt: new Date(), + suspendedAt: new Date(), + state: descriptorState.suspended, + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [suspendedDescriptor], + }; + + const payload: EServiceDescriptorUpdatedV1 = { + eserviceId: eservice.id, + eserviceDescriptor: toDescriptorV1(suspendedDescriptor), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 2, + type: "EServiceDescriptorUpdated", + event_version: 1, + data: payload, + log_date: new Date(), + }; + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: suspendedDescriptor.id, + }); + const previousStateEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.active, + descriptorAudience: suspendedDescriptor.audience[0], + descriptorVoucherLifespan: suspendedDescriptor.voucherLifespan, + version: 3, + updatedAt: new Date().toISOString(), + }; + + await writeCatalogEntry(previousStateEntry, dynamoDBClient); + + // token-generation-states + const tokenStateEntryPK1 = + makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const tokenStateEntryPK2 = + makeTokenGenerationStatesClientKidPurposePK({ + clientId: generateId(), + kid: `kid ${Math.random()}`, + purposeId: generateId(), + }); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: suspendedDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.active, + descriptorAudience: suspendedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.active, + descriptorAudience: suspendedDescriptor.audience[0], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + + await handleMessageV1(message, dynamoDBClient); + await sleep(1000, mockDate); + + const retrievedEntry = await readCatalogEntry( + primaryKey, + dynamoDBClient + ); + expect(retrievedEntry).toEqual(previousStateEntry); + + // token-generation-states + const retrievedTokenStateEntries = + await readTokenStateEntriesByEserviceIdAndDescriptorId( + eserviceId_descriptorId, + dynamoDBClient + ); + expect(retrievedTokenStateEntries).toEqual( + expect.arrayContaining([ + previousTokenStateEntry1, + previousTokenStateEntry2, + ]) + ); + }); + + it("should do no operation if previous entry doesn't exist", async () => { + const suspendedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it"], + interface: getMockDocument(), + publishedAt: new Date(), + suspendedAt: new Date(), + state: descriptorState.suspended, + }; + + const eservice: EService = { + ...getMockEService(), + descriptors: [suspendedDescriptor], + }; + + const payload: EServiceDescriptorUpdatedV1 = { + eserviceId: eservice.id, + eserviceDescriptor: toDescriptorV1(suspendedDescriptor), + }; + const message: EServiceEventEnvelope = { + sequence_num: 1, + stream_id: eservice.id, + version: 2, + type: "EServiceDescriptorUpdated", + event_version: 1, + data: payload, + log_date: new Date(), + }; + + await handleMessageV1(message, dynamoDBClient); + + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: suspendedDescriptor.id, + }); + + const retrievedEntry = await readCatalogEntry( + primaryKey, + dynamoDBClient + ); + expect(retrievedEntry).toBeUndefined(); + }); }); }); - it("EServiceDescriptorUpdated (published -> archived)", async () => { + it("(published -> archived) should update the entry", async () => { const archivedDescriptor: Descriptor = { ...getMockDescriptor(), audience: ["pagopa.it"], From b0a0be94c79382346da987c283f4600bc2d1b72b Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 14:15:24 +0200 Subject: [PATCH 221/241] Remove api spec --- .../open-api/authorizationServerApi.yml | 215 ------------------ packages/api-clients/src/index.ts | 1 - 2 files changed, 216 deletions(-) delete mode 100644 packages/api-clients/open-api/authorizationServerApi.yml diff --git a/packages/api-clients/open-api/authorizationServerApi.yml b/packages/api-clients/open-api/authorizationServerApi.yml deleted file mode 100644 index 3fcbf37b1e..0000000000 --- a/packages/api-clients/open-api/authorizationServerApi.yml +++ /dev/null @@ -1,215 +0,0 @@ -openapi: 3.0.3 -info: - title: Interoperability Authorization Server Micro Service - description: Provides endpoints to request an interoperability token - version: "0.1.0" - contact: - name: API Support - url: "http://www.example.com/support" - email: support@example.com - termsOfService: "http://swagger.io/terms/" - x-api-id: an x-api-id - x-summary: an x-summary -servers: - - url: "/authorization-server" - description: Interoperability Authorization Server -tags: - - name: auth - description: Get security information - externalDocs: - description: Find out more - url: http://swagger.io - - name: health - description: Verify service status - externalDocs: - description: Find out more - url: http://swagger.io -paths: - "/token.oauth2": - post: - tags: - - auth - summary: Create a new access token - description: Return the generated access token - operationId: createToken - requestBody: - required: true - content: - application/x-www-form-urlencoded: - schema: - $ref: "#/components/schemas/AccessTokenRequest" - responses: - "200": - description: The Access token - headers: - Cache-Control: - schema: - type: string - default: no-cache, no-store - description: no-cache, no-store - "X-Rate-Limit-Limit": - schema: - type: integer - description: Max allowed requests within time interval - "X-Rate-Limit-Remaining": - schema: - type: integer - description: Remaining requests within time interval - "X-Rate-Limit-Interval": - schema: - type: integer - description: Time interval in milliseconds. Allowed requests will be constantly replenished during the interval. At the end of the interval the max allowed requests will be available - content: - application/json: - schema: - $ref: "#/components/schemas/ClientCredentialsResponse" - "400": - description: Bad request - x-noqa: RFC6749 - content: - application/json: - schema: - $ref: "#/components/schemas/Problem" - "401": - description: Unauthorized - x-noqa: RFC6749 - content: - application/json: - schema: - $ref: "#/components/schemas/Problem" - "429": - description: Too Many Requests - content: - application/json: - schema: - $ref: "#/components/schemas/Problem" - headers: - "X-Rate-Limit-Limit": - schema: - type: integer - description: Max allowed requests within time interval - "X-Rate-Limit-Remaining": - schema: - type: integer - description: Remaining requests within time interval - "X-Rate-Limit-Interval": - schema: - type: integer - description: Time interval in milliseconds. Allowed requests will be constantly replenished during the interval. At the end of the interval the max allowed requests will be available - /status: - get: - security: [] - summary: Returns the application status - description: Returns the application status - operationId: get_status - tags: - - health - responses: - "200": - description: This is the valid status from the server. - content: - application/problem+json: - schema: - $ref: "#/components/schemas/Problem" -components: - schemas: - AccessTokenRequest: - type: object - required: - - client_assertion - - client_assertion_type - - grant_type - properties: - client_id: - type: string - example: e58035ce-c753-4f72-b613-46f8a17b71cc - client_assertion: - type: string - format: jws - client_assertion_type: - type: string - example: urn:ietf:params:oauth:client-assertion-type:jwt-bearer - grant_type: - type: string - enum: - - client_credentials - TokenType: - type: string - description: Represents the token type - enum: - - Bearer - ClientCredentialsResponse: - type: object - required: - - access_token - - token_type - - expires_in - properties: - access_token: - type: string - format: jws - token_type: - $ref: "#/components/schemas/TokenType" - expires_in: - type: integer - format: int32 - maximum: 600 - Problem: - properties: - type: - description: URI reference of type definition - type: string - status: - description: The HTTP status code generated by the origin server for this occurrence of the problem. - example: 400 - exclusiveMaximum: true - format: int32 - maximum: 600 - minimum: 100 - type: integer - title: - description: A short, summary of the problem type. Written in english and readable - example: Service Unavailable - maxLength: 64 - pattern: "^[ -~]{0,64}$" - type: string - correlationId: - description: Unique identifier of the request - example: "53af4f2d-0c87-41ef-a645-b726a821852b" - maxLength: 64 - type: string - detail: - description: A human readable explanation of the problem. - example: Request took too long to complete. - maxLength: 4096 - pattern: "^.{0,1024}$" - type: string - errors: - type: array - minItems: 0 - items: - $ref: "#/components/schemas/ProblemError" - additionalProperties: false - required: - - type - - status - - title - - errors - ProblemError: - properties: - code: - description: Internal code of the error - example: 123-4567 - minLength: 8 - maxLength: 8 - pattern: "^[0-9]{3}-[0-9]{4}$" - type: string - detail: - description: A human readable explanation specific to this occurrence of the problem. - example: Parameter not valid - maxLength: 4096 - pattern: "^.{0,1024}$" - type: string - required: - - code - - detail diff --git a/packages/api-clients/src/index.ts b/packages/api-clients/src/index.ts index 7bb23138a5..e5ada0a70e 100644 --- a/packages/api-clients/src/index.ts +++ b/packages/api-clients/src/index.ts @@ -8,4 +8,3 @@ export * as purposeApi from "./generated/purposeApi.js"; export * as selfcareV2ClientApi from "./generated/selfcareV2ClientApi.js"; export * as tenantApi from "./generated/tenantApi.js"; export * from "./selfcareClients.js"; -export * as authorizationServerApi from "./generated/authorizationServerApi.js"; From 1b8b6840d0e9ddbca7efe8b7ba593d45cff474de Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 14:32:37 +0200 Subject: [PATCH 222/241] Refactor --- packages/client-assertion-validation/src/types.ts | 11 ++++++----- packages/client-assertion-validation/src/utils.ts | 2 +- packages/client-assertion-validation/test/utils.ts | 10 ++++++---- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/client-assertion-validation/src/types.ts b/packages/client-assertion-validation/src/types.ts index 1047ee232f..aad474a4ee 100644 --- a/packages/client-assertion-validation/src/types.ts +++ b/packages/client-assertion-validation/src/types.ts @@ -11,6 +11,7 @@ import { import { z } from "zod"; import { ErrorCodes } from "./errors.js"; import { + ALLOWED_ALGORITHM, EXPECTED_CLIENT_ASSERTION_TYPE, EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE, } from "./utils.js"; @@ -26,7 +27,7 @@ export type ClientAssertionDigest = z.infer; export const ClientAssertionHeader = z .object({ kid: z.string(), - alg: z.string(), // TODO Enum, which values? + alg: z.string(), }) .strict(); export type ClientAssertionHeader = z.infer; @@ -58,20 +59,20 @@ export const Key = z clientId: ClientId, consumerId: TenantId, kid: z.string(), - purposeId: PurposeId, // TODO which field of the table is mapped to this? + purposeId: PurposeId, publicKey: z.string().min(1), - algorithm: z.literal("RS256"), // no field to map from the table. Is it extracted from publicKey field? + algorithm: z.literal(ALLOWED_ALGORITHM), }) .strict(); export type Key = z.infer; export const ConsumerKey = Key.extend({ clientKind: z.literal(clientKindTokenStates.consumer), - purposeId: PurposeId, // TODO is this naming ok? + purposeId: PurposeId, purposeState: ItemState, agreementId: AgreementId, agreementState: ItemState, - eServiceId: EServiceId, // no field to map. Extract from GSIPK_eserviceId_descriptorId? + eServiceId: EServiceId, descriptorState: ItemState, }).strict(); export type ConsumerKey = z.infer; diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index c11dcd61ab..9f90e63244 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -41,7 +41,7 @@ const CLIENT_ASSERTION_AUDIENCE = "test.interop.pagopa.it"; // TODO: env? export const EXPECTED_CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; // TODO: env? export const EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE = "client_credentials"; // TODO: env? -const ALLOWED_ALGORITHM = "RS256"; +export const ALLOWED_ALGORITHM = "RS256"; const ALLOWED_DIGEST_ALGORITHM = "SHA256"; export const validateJti = (jti?: string): ValidationResult => { diff --git a/packages/client-assertion-validation/test/utils.ts b/packages/client-assertion-validation/test/utils.ts index 833dcf48b0..b9eaf0b3b1 100644 --- a/packages/client-assertion-validation/test/utils.ts +++ b/packages/client-assertion-validation/test/utils.ts @@ -14,6 +14,10 @@ import { ClientAssertionValidationRequest, ConsumerKey, } from ".././src/types.js"; +import { + EXPECTED_CLIENT_ASSERTION_TYPE, + EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE, +} from "../src/utils.js"; export const value64chars = crypto.randomBytes(32).toString("hex"); @@ -98,15 +102,13 @@ export const getMockAccessTokenRequest = return { client_id: generateId(), - // TODO: change to env variable - client_assertion_type: - "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", + client_assertion_type: EXPECTED_CLIENT_ASSERTION_TYPE, client_assertion: getMockClientAssertion({ customHeader: {}, payload: {}, customClaims: {}, keySet, }), - grant_type: "client_credentials", + grant_type: EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE, }; }; From 0a046dc3a8ff75a40089e3413b1475f2e63f1b3c Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 14:32:51 +0200 Subject: [PATCH 223/241] Remove comment --- .../client-assertion-validation/src/errors.ts | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/packages/client-assertion-validation/src/errors.ts b/packages/client-assertion-validation/src/errors.ts index 70090c11e2..4e5b69e7e3 100644 --- a/packages/client-assertion-validation/src/errors.ts +++ b/packages/client-assertion-validation/src/errors.ts @@ -40,26 +40,6 @@ export const errorCodes = { export type ErrorCodes = keyof typeof errorCodes; -// Notes about errors: -// - InvalidClientIdFormat -> check on uuid -// - ClientAssertionParseFailed -> already handled in invalidClientAssertionFormat X -// - ClientAssertionInvalidClaims -> should be covered by individual checks ? -// - InvalidSubjectFormat -> check on uuid of subject claim -// - InvalidPurposeIdFormat -> check on uuid of purposeId claim (already covered by invalidPurposeIdClaimFormat?) -// - DigestClaimNotFound -> check if custom claim digest exists -// - InvalidDigestClaims -> check if digest has only {alg, value} keys X we aren't discriminating between different safeParse errors -// - InvalidDigestFormat -> check object shape -// - InvalidHashLength -> check on the length of digest.value (digest is a custom claim) -// - InvalidHashAlgorithm -> check if digest.alg is sha256 -// - AlgorithmNotFound -> check if header.alg is present -// - AlgorithmNotAllowed -> check if (header.alg === RS256) -// - PublicKeyParseFailed -> out of scope for this module X -// - ClientAssertionVerificationError -> maybe too generic X -// - InvalidClientAssertionSignature -> maybe already covered by existing cases X -// - PurposeIdNotProvided -> based on entry type (Api client doesn't need purposeId) -// - PurposeNotFound -> related to previous, check if there is a purpose entry for that purposeId (in platform states) needed in this package? -// - InvalidKidFormat -> Verify that kid does not contain special characters - export function clientAssertionValidationFailure( details: string ): ApiError { From b39d50fc16033a95ed9919bf2ab78ddecfd25c76 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 15:04:30 +0200 Subject: [PATCH 224/241] Remove commented code --- packages/client-assertion-validation/src/utils.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index 9f90e63244..1a7b51cd9c 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -171,9 +171,6 @@ export const validateDigest = ( return failedValidation([digestLengthError, digestAlgError]); }; -// export const b64Decode = (str: string): string => -// Buffer.from(str, "base64").toString("binary"); - export const validatePlatformState = ( key: ConsumerKey ): ValidationResult => { From 278059465909fba6f9d774c96a17f8eb6c96883a Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 15:05:34 +0200 Subject: [PATCH 225/241] Fix comments --- .../test/validation.test.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/client-assertion-validation/test/validation.test.ts b/packages/client-assertion-validation/test/validation.test.ts index 68535454a6..4925892ad6 100644 --- a/packages/client-assertion-validation/test/validation.test.ts +++ b/packages/client-assertion-validation/test/validation.test.ts @@ -67,7 +67,6 @@ describe("validation test", () => { // it("invalidAssertionType", () => { // TODO how to test this if "something-wrong" can't be assigned to the property? // possible solution: the property is a string (not literal) and the check is done later - // const wrongAssertionType = "something-wrong"; // const request = { // ...getMockAccessTokenRequest(), @@ -78,7 +77,10 @@ describe("validation test", () => { // expect(errors).toHaveLength(1); // expect(errors![0]).toEqual(invalidAssertionType(wrongAssertionType)); // }); + // it("invalidGrantType", () => { + // TODO how to test this if "something-wrong" can't be assigned to the property? + // possible solution: the property is a string (not literal) and the check is done later // const wrongGrantType = "something-wrong"; // const request = { // ...getMockAccessTokenRequest(), @@ -600,6 +602,7 @@ describe("validation test", () => { }); // it("unexpectedKeyType (apiKey and clientKindTokenStates.consumer)", () => { + // // How to test this? The goal is to pass an api key to validateClientKindAndPlatformState (with kind clientKindTokenStates.consumer) // const mockApiKey = { // ...getMockApiKey(), // clientKind: clientKindTokenStates.consumer, @@ -713,9 +716,3 @@ describe("validation test", () => { }); }); }); - -// const printErrors = (errors?: Array>): void => { -// if (errors) { -// errors.forEach((e) => console.log(e.code, e.detail)); -// } -// }; From 4bfcfd72013e0085d27e4fde827b0484396392cd Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 15:05:42 +0200 Subject: [PATCH 226/241] Revert string literal --- packages/client-assertion-validation/src/types.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/client-assertion-validation/src/types.ts b/packages/client-assertion-validation/src/types.ts index aad474a4ee..a59abc6ee9 100644 --- a/packages/client-assertion-validation/src/types.ts +++ b/packages/client-assertion-validation/src/types.ts @@ -11,7 +11,6 @@ import { import { z } from "zod"; import { ErrorCodes } from "./errors.js"; import { - ALLOWED_ALGORITHM, EXPECTED_CLIENT_ASSERTION_TYPE, EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE, } from "./utils.js"; @@ -61,7 +60,7 @@ export const Key = z kid: z.string(), purposeId: PurposeId, publicKey: z.string().min(1), - algorithm: z.literal(ALLOWED_ALGORITHM), + algorithm: z.literal("RS256"), }) .strict(); export type Key = z.infer; From 6c445ede691c3f9c9daa683addbf520857fd29c9 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Tue, 24 Sep 2024 15:27:51 +0200 Subject: [PATCH 227/241] Add env var --- packages/client-assertion-validation/.env | 1 + packages/client-assertion-validation/src/config.ts | 11 +++++++++++ packages/client-assertion-validation/src/utils.ts | 7 ++++--- 3 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 packages/client-assertion-validation/.env create mode 100644 packages/client-assertion-validation/src/config.ts diff --git a/packages/client-assertion-validation/.env b/packages/client-assertion-validation/.env new file mode 100644 index 0000000000..3df4f8559a --- /dev/null +++ b/packages/client-assertion-validation/.env @@ -0,0 +1 @@ +CLIENT_ASSERTION_AUDIENCE="test.interop.pagopa.it" diff --git a/packages/client-assertion-validation/src/config.ts b/packages/client-assertion-validation/src/config.ts new file mode 100644 index 0000000000..489497b593 --- /dev/null +++ b/packages/client-assertion-validation/src/config.ts @@ -0,0 +1,11 @@ +import { z } from "zod"; + +const ClientAssertionValidationConfig = z + .object({ + CLIENT_ASSERTION_AUDIENCE: z.string(), + }) + .transform((c) => ({ + clientAssertionAudience: c.CLIENT_ASSERTION_AUDIENCE, + })); + +export const config = ClientAssertionValidationConfig.parse(process.env); diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index 1a7b51cd9c..ae9fdccf16 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -37,7 +37,8 @@ import { invalidHashAlgorithm, invalidKidFormat, } from "./errors.js"; -const CLIENT_ASSERTION_AUDIENCE = "test.interop.pagopa.it"; // TODO: env? +import { config } from "./config.js"; + export const EXPECTED_CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; // TODO: env? export const EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE = "client_credentials"; // TODO: env? @@ -123,14 +124,14 @@ export const validateKid = (kid?: string): ValidationResult => { export const validateAudience = ( aud: string | string[] | undefined ): ValidationResult => { - if (aud === CLIENT_ASSERTION_AUDIENCE) { + if (aud === config.clientAssertionAudience) { return successfulValidation([aud]); } if (!Array.isArray(aud)) { return failedValidation([invalidAudienceFormat()]); } - if (!aud.includes(CLIENT_ASSERTION_AUDIENCE)) { + if (!aud.includes(config.clientAssertionAudience)) { return failedValidation([invalidAudience()]); } return successfulValidation(aud); From 6917d5234756771c9ec2560de0ca57b3ce4e964a Mon Sep 17 00:00:00 2001 From: Carmine Porricelli Date: Tue, 24 Sep 2024 18:02:57 +0200 Subject: [PATCH 228/241] WIP, broken commit --- .../client-assertion-validation/src/types.ts | 3 +- .../client-assertion-validation/src/utils.ts | 20 +- .../src/validation.ts | 171 ++++++++++++------ 3 files changed, 117 insertions(+), 77 deletions(-) diff --git a/packages/client-assertion-validation/src/types.ts b/packages/client-assertion-validation/src/types.ts index a59abc6ee9..c6a882c626 100644 --- a/packages/client-assertion-validation/src/types.ts +++ b/packages/client-assertion-validation/src/types.ts @@ -83,10 +83,9 @@ export type ApiKey = z.infer; export type ValidationResult = SuccessfulValidation | FailedValidation; -export type SuccessfulValidation = { errors: undefined; data: T }; +export type SuccessfulValidation = { data: T }; export type FailedValidation = { errors: Array>; - data: undefined; }; export const ClientAssertionValidationRequest = z.object({ diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index ae9fdccf16..19d6863193 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -194,22 +194,10 @@ export const successfulValidation = ( result: T ): SuccessfulValidation => ({ data: result, - errors: undefined, }); export const failedValidation = ( - // errors: [[error1, error2, undefined], error3, undefined] - errors: Array< - Array | undefined> | ApiError | undefined - > -): FailedValidation => { - const nestedArrayWithoutUndefined = errors.filter((a) => a !== undefined); - const flattenedArray = nestedArrayWithoutUndefined.flat(1); - const flattenedArrayWithoutUndefined = flattenedArray.filter( - (e) => e !== undefined - ); - return { - data: undefined, - errors: flattenedArrayWithoutUndefined as Array>, - }; -}; + errors: Array | undefined> +): FailedValidation => ({ + errors: errors.filter((v): v is NonNullable => Boolean(v)), +}); diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index 2ab35847c1..bdf5c9ae56 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -106,6 +106,34 @@ export const validateRequestParameters = ( return failedValidation([assertionTypeError, grantTypeError]); }; +type ValidationOutput = { + jti: ValidationResult; + iat: ValidationResult; +}; + +function isValidationSuccess( + validationResult: ValidationResult +): validationResult is Extract { + return "data" in validationResult && Boolean(validationResult.data); +} + +function isValidationError( + validationResult: ValidationResult +): validationResult is Extract { + return "errors" in validationResult && Array.isArray(validationResult.errors); +} + +function isAllValidationSuccess( + validationOutput: ValidationOutput +): validationOutput is { + [TKey in keyof typeof validationOutput]: Extract< + (typeof validationOutput)[TKey], + { data: unknown } + >; +} { + return Object.values(validationOutput).every(isValidationSuccess); +} + // eslint-disable-next-line complexity export const verifyClientAssertion = ( clientAssertionJws: string, @@ -121,48 +149,14 @@ export const verifyClientAssertion = ( return failedValidation([unexpectedClientAssertionPayload()]); } - const { errors: jtiErrors, data: validatedJti } = validateJti( - decoded.payload.jti - ); - const { errors: iatErrors, data: validatedIat } = validateIat( - decoded.payload.iat - ); - const { errors: expErrors, data: validatedExp } = validateExp( - decoded.payload.exp - ); - const { errors: issErrors, data: validatedIss } = validateIss( - decoded.payload.iss - ); - const { errors: subErrors, data: validatedSub } = validateSub( - decoded.payload.sub, - clientId - ); - const { errors: purposeIdErrors, data: validatedPurposeId } = - validatePurposeId(decoded.payload.purposeId); - const { errors: kidErrors, data: validatedKid } = validateKid( - decoded.header.kid - ); - const { errors: audErrors, data: validatedAud } = validateAudience( - decoded.payload.aud - ); - const { errors: algErrors, data: validatedAlg } = validateAlgorithm( - decoded.header.alg - ); - const { errors: digestErrors, data: validatedDigest } = validateDigest( - decoded.payload.digest - ); - if ( - !jtiErrors && - !iatErrors && - !expErrors && - !issErrors && - !subErrors && - !purposeIdErrors && - !kidErrors && - !audErrors && - !algErrors && - !digestErrors - ) { + const validationOutput: ValidationOutput = { + jti: validateJti(decoded.payload.jti), + iat: validateIat(decoded.payload.iat), + }; + + if (isAllValidationSuccess(validationOutput)) { + // build client assertion + const result: ClientAssertion = { header: { kid: validatedKid, @@ -171,8 +165,8 @@ export const verifyClientAssertion = ( payload: { sub: validatedSub, purposeId: validatedPurposeId, - jti: validatedJti, - iat: validatedIat, + jti: validationOutput.jti.data, + iat: validationOutput.iat.data, iss: validatedIss, aud: validatedAud, exp: validatedExp, @@ -181,18 +175,11 @@ export const verifyClientAssertion = ( }; return successfulValidation(result); } - return failedValidation([ - jtiErrors, - iatErrors, - expErrors, - issErrors, - subErrors, - purposeIdErrors, - kidErrors, - audErrors, - algErrors, - digestErrors, - ]); + + const errors = Object.values(validationOutput) + .filter(isValidationError) + .flatMap(({ errors }) => errors); + return failedValidation(errors); } catch (error) { return failedValidation([unexpectedClientAssertionPayload()]); } @@ -240,9 +227,14 @@ export const validateClientKindAndPlatformState = ( ) .with(clientKindTokenStates.consumer, () => { if (ConsumerKey.safeParse(key).success) { - const { errors: platformStateErrors } = validatePlatformState( - key as ConsumerKey - ); + const validationResult = validatePlatformState(key as ConsumerKey); + + if (isValidationSuccess(validationResult)) { + // ... + } else { + validationResult.errors; + } + const purposeIdError = jwt.payload.purposeId ? undefined : purposeIdNotProvided(); @@ -257,3 +249,64 @@ export const validateClientKindAndPlatformState = ( ]); }) .exhaustive(); + +// const { errors: jtiErrors, data: validatedJti } = validateJti( +// decoded.payload.jti +// ); +// const { errors: iatErrors, data: validatedIat } = validateIat( +// decoded.payload.iat +// ); +// const { errors: expErrors, data: validatedExp } = validateExp( +// decoded.payload.exp +// ); +// const { errors: issErrors, data: validatedIss } = validateIss( +// decoded.payload.iss +// ); +// const { errors: subErrors, data: validatedSub } = validateSub( +// decoded.payload.sub, +// clientId +// ); +// const { errors: purposeIdErrors, data: validatedPurposeId } = +// validatePurposeId(decoded.payload.purposeId); +// const { errors: kidErrors, data: validatedKid } = validateKid( +// decoded.header.kid +// ); +// const { errors: audErrors, data: validatedAud } = validateAudience( +// decoded.payload.aud +// ); +// const { errors: algErrors, data: validatedAlg } = validateAlgorithm( +// decoded.header.alg +// ); +// const { errors: digestErrors, data: validatedDigest } = validateDigest( +// decoded.payload.digest +// ); +// if ( +// !jtiErrors && +// !iatErrors && +// !expErrors && +// !issErrors && +// !subErrors && +// !purposeIdErrors && +// !kidErrors && +// !audErrors && +// !algErrors && +// !digestErrors +// ) { +// const result: ClientAssertion = { +// header: { +// kid: validatedKid, +// alg: validatedAlg, +// }, +// payload: { +// sub: validatedSub, +// purposeId: validatedPurposeId, +// jti: validatedJti, +// iat: validatedIat, +// iss: validatedIss, +// aud: validatedAud, +// exp: validatedExp, +// digest: validatedDigest, +// }, +// }; +// return successfulValidation(result); +// } From 09e27ea86f88a8f2ce61da6fdc7ef0030e5d622d Mon Sep 17 00:00:00 2001 From: Carmine Porricelli Date: Wed, 25 Sep 2024 09:56:32 +0200 Subject: [PATCH 229/241] Fix types --- .../client-assertion-validation/src/validation.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index bdf5c9ae56..890992a84c 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -30,7 +30,9 @@ import { ClientAssertion, ClientAssertionValidationRequest, ConsumerKey, + FailedValidation, Key, + SuccessfulValidation, ValidationResult, } from "./types.js"; import { @@ -113,23 +115,20 @@ type ValidationOutput = { function isValidationSuccess( validationResult: ValidationResult -): validationResult is Extract { - return "data" in validationResult && Boolean(validationResult.data); +): validationResult is SuccessfulValidation { + return "data" in validationResult && validationResult.data !== undefined; } function isValidationError( validationResult: ValidationResult -): validationResult is Extract { +): validationResult is FailedValidation { return "errors" in validationResult && Array.isArray(validationResult.errors); } function isAllValidationSuccess( validationOutput: ValidationOutput ): validationOutput is { - [TKey in keyof typeof validationOutput]: Extract< - (typeof validationOutput)[TKey], - { data: unknown } - >; + [TKey in keyof ValidationOutput]: SuccessfulValidation; } { return Object.values(validationOutput).every(isValidationSuccess); } @@ -230,6 +229,7 @@ export const validateClientKindAndPlatformState = ( const validationResult = validatePlatformState(key as ConsumerKey); if (isValidationSuccess(validationResult)) { + validationResult.data; // ... } else { validationResult.errors; From 1bbe00c8b65d22eb96f0dcb18f464271e0af6cd9 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 25 Sep 2024 10:09:11 +0200 Subject: [PATCH 230/241] Revert "Fix types" This reverts commit 09e27ea86f88a8f2ce61da6fdc7ef0030e5d622d. --- .../client-assertion-validation/src/validation.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index 890992a84c..bdf5c9ae56 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -30,9 +30,7 @@ import { ClientAssertion, ClientAssertionValidationRequest, ConsumerKey, - FailedValidation, Key, - SuccessfulValidation, ValidationResult, } from "./types.js"; import { @@ -115,20 +113,23 @@ type ValidationOutput = { function isValidationSuccess( validationResult: ValidationResult -): validationResult is SuccessfulValidation { - return "data" in validationResult && validationResult.data !== undefined; +): validationResult is Extract { + return "data" in validationResult && Boolean(validationResult.data); } function isValidationError( validationResult: ValidationResult -): validationResult is FailedValidation { +): validationResult is Extract { return "errors" in validationResult && Array.isArray(validationResult.errors); } function isAllValidationSuccess( validationOutput: ValidationOutput ): validationOutput is { - [TKey in keyof ValidationOutput]: SuccessfulValidation; + [TKey in keyof typeof validationOutput]: Extract< + (typeof validationOutput)[TKey], + { data: unknown } + >; } { return Object.values(validationOutput).every(isValidationSuccess); } @@ -229,7 +230,6 @@ export const validateClientKindAndPlatformState = ( const validationResult = validatePlatformState(key as ConsumerKey); if (isValidationSuccess(validationResult)) { - validationResult.data; // ... } else { validationResult.errors; From ebb6db9091311c8644cf0c70996390561aa7abd9 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 25 Sep 2024 10:56:52 +0200 Subject: [PATCH 231/241] Revert "WIP, broken commit" This reverts commit 6917d5234756771c9ec2560de0ca57b3ce4e964a. --- .../client-assertion-validation/src/types.ts | 3 +- .../client-assertion-validation/src/utils.ts | 20 +- .../src/validation.ts | 171 ++++++------------ 3 files changed, 77 insertions(+), 117 deletions(-) diff --git a/packages/client-assertion-validation/src/types.ts b/packages/client-assertion-validation/src/types.ts index c6a882c626..a59abc6ee9 100644 --- a/packages/client-assertion-validation/src/types.ts +++ b/packages/client-assertion-validation/src/types.ts @@ -83,9 +83,10 @@ export type ApiKey = z.infer; export type ValidationResult = SuccessfulValidation | FailedValidation; -export type SuccessfulValidation = { data: T }; +export type SuccessfulValidation = { errors: undefined; data: T }; export type FailedValidation = { errors: Array>; + data: undefined; }; export const ClientAssertionValidationRequest = z.object({ diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index 19d6863193..ae9fdccf16 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -194,10 +194,22 @@ export const successfulValidation = ( result: T ): SuccessfulValidation => ({ data: result, + errors: undefined, }); export const failedValidation = ( - errors: Array | undefined> -): FailedValidation => ({ - errors: errors.filter((v): v is NonNullable => Boolean(v)), -}); + // errors: [[error1, error2, undefined], error3, undefined] + errors: Array< + Array | undefined> | ApiError | undefined + > +): FailedValidation => { + const nestedArrayWithoutUndefined = errors.filter((a) => a !== undefined); + const flattenedArray = nestedArrayWithoutUndefined.flat(1); + const flattenedArrayWithoutUndefined = flattenedArray.filter( + (e) => e !== undefined + ); + return { + data: undefined, + errors: flattenedArrayWithoutUndefined as Array>, + }; +}; diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index bdf5c9ae56..2ab35847c1 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -106,34 +106,6 @@ export const validateRequestParameters = ( return failedValidation([assertionTypeError, grantTypeError]); }; -type ValidationOutput = { - jti: ValidationResult; - iat: ValidationResult; -}; - -function isValidationSuccess( - validationResult: ValidationResult -): validationResult is Extract { - return "data" in validationResult && Boolean(validationResult.data); -} - -function isValidationError( - validationResult: ValidationResult -): validationResult is Extract { - return "errors" in validationResult && Array.isArray(validationResult.errors); -} - -function isAllValidationSuccess( - validationOutput: ValidationOutput -): validationOutput is { - [TKey in keyof typeof validationOutput]: Extract< - (typeof validationOutput)[TKey], - { data: unknown } - >; -} { - return Object.values(validationOutput).every(isValidationSuccess); -} - // eslint-disable-next-line complexity export const verifyClientAssertion = ( clientAssertionJws: string, @@ -149,14 +121,48 @@ export const verifyClientAssertion = ( return failedValidation([unexpectedClientAssertionPayload()]); } - const validationOutput: ValidationOutput = { - jti: validateJti(decoded.payload.jti), - iat: validateIat(decoded.payload.iat), - }; - - if (isAllValidationSuccess(validationOutput)) { - // build client assertion - + const { errors: jtiErrors, data: validatedJti } = validateJti( + decoded.payload.jti + ); + const { errors: iatErrors, data: validatedIat } = validateIat( + decoded.payload.iat + ); + const { errors: expErrors, data: validatedExp } = validateExp( + decoded.payload.exp + ); + const { errors: issErrors, data: validatedIss } = validateIss( + decoded.payload.iss + ); + const { errors: subErrors, data: validatedSub } = validateSub( + decoded.payload.sub, + clientId + ); + const { errors: purposeIdErrors, data: validatedPurposeId } = + validatePurposeId(decoded.payload.purposeId); + const { errors: kidErrors, data: validatedKid } = validateKid( + decoded.header.kid + ); + const { errors: audErrors, data: validatedAud } = validateAudience( + decoded.payload.aud + ); + const { errors: algErrors, data: validatedAlg } = validateAlgorithm( + decoded.header.alg + ); + const { errors: digestErrors, data: validatedDigest } = validateDigest( + decoded.payload.digest + ); + if ( + !jtiErrors && + !iatErrors && + !expErrors && + !issErrors && + !subErrors && + !purposeIdErrors && + !kidErrors && + !audErrors && + !algErrors && + !digestErrors + ) { const result: ClientAssertion = { header: { kid: validatedKid, @@ -165,8 +171,8 @@ export const verifyClientAssertion = ( payload: { sub: validatedSub, purposeId: validatedPurposeId, - jti: validationOutput.jti.data, - iat: validationOutput.iat.data, + jti: validatedJti, + iat: validatedIat, iss: validatedIss, aud: validatedAud, exp: validatedExp, @@ -175,11 +181,18 @@ export const verifyClientAssertion = ( }; return successfulValidation(result); } - - const errors = Object.values(validationOutput) - .filter(isValidationError) - .flatMap(({ errors }) => errors); - return failedValidation(errors); + return failedValidation([ + jtiErrors, + iatErrors, + expErrors, + issErrors, + subErrors, + purposeIdErrors, + kidErrors, + audErrors, + algErrors, + digestErrors, + ]); } catch (error) { return failedValidation([unexpectedClientAssertionPayload()]); } @@ -227,14 +240,9 @@ export const validateClientKindAndPlatformState = ( ) .with(clientKindTokenStates.consumer, () => { if (ConsumerKey.safeParse(key).success) { - const validationResult = validatePlatformState(key as ConsumerKey); - - if (isValidationSuccess(validationResult)) { - // ... - } else { - validationResult.errors; - } - + const { errors: platformStateErrors } = validatePlatformState( + key as ConsumerKey + ); const purposeIdError = jwt.payload.purposeId ? undefined : purposeIdNotProvided(); @@ -249,64 +257,3 @@ export const validateClientKindAndPlatformState = ( ]); }) .exhaustive(); - -// const { errors: jtiErrors, data: validatedJti } = validateJti( -// decoded.payload.jti -// ); -// const { errors: iatErrors, data: validatedIat } = validateIat( -// decoded.payload.iat -// ); -// const { errors: expErrors, data: validatedExp } = validateExp( -// decoded.payload.exp -// ); -// const { errors: issErrors, data: validatedIss } = validateIss( -// decoded.payload.iss -// ); -// const { errors: subErrors, data: validatedSub } = validateSub( -// decoded.payload.sub, -// clientId -// ); -// const { errors: purposeIdErrors, data: validatedPurposeId } = -// validatePurposeId(decoded.payload.purposeId); -// const { errors: kidErrors, data: validatedKid } = validateKid( -// decoded.header.kid -// ); -// const { errors: audErrors, data: validatedAud } = validateAudience( -// decoded.payload.aud -// ); -// const { errors: algErrors, data: validatedAlg } = validateAlgorithm( -// decoded.header.alg -// ); -// const { errors: digestErrors, data: validatedDigest } = validateDigest( -// decoded.payload.digest -// ); -// if ( -// !jtiErrors && -// !iatErrors && -// !expErrors && -// !issErrors && -// !subErrors && -// !purposeIdErrors && -// !kidErrors && -// !audErrors && -// !algErrors && -// !digestErrors -// ) { -// const result: ClientAssertion = { -// header: { -// kid: validatedKid, -// alg: validatedAlg, -// }, -// payload: { -// sub: validatedSub, -// purposeId: validatedPurposeId, -// jti: validatedJti, -// iat: validatedIat, -// iss: validatedIss, -// aud: validatedAud, -// exp: validatedExp, -// digest: validatedDigest, -// }, -// }; -// return successfulValidation(result); -// } From f05c1c74291f2e8d256906222db1e31fd3031e59 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 25 Sep 2024 13:56:57 +0200 Subject: [PATCH 232/241] Fix model --- .../token-generation-readmodel/token-generation-states-entry.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts b/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts index fa9a9b755e..12ad6dbfae 100644 --- a/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts +++ b/packages/models/src/token-generation-readmodel/token-generation-states-entry.ts @@ -37,6 +37,7 @@ export const TokenGenerationStatesClientPurposeEntry = GSIPK_eserviceId_descriptorId: GSIPKEServiceIdDescriptorId.optional(), descriptorState: ItemState.optional(), descriptorAudience: z.string().optional(), + descriptorVoucherLifespan: z.number().optional(), GSIPK_purposeId: PurposeId.optional(), purposeState: ItemState.optional(), purposeVersionId: PurposeVersionId.optional(), From 98cd82e72cf95e0b1f9bcfffeee406a50375707a Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 25 Sep 2024 15:07:38 +0200 Subject: [PATCH 233/241] Reapply "WIP, broken commit" This reverts commit ebb6db9091311c8644cf0c70996390561aa7abd9. --- .../client-assertion-validation/src/types.ts | 3 +- .../client-assertion-validation/src/utils.ts | 20 +- .../src/validation.ts | 171 ++++++++++++------ 3 files changed, 117 insertions(+), 77 deletions(-) diff --git a/packages/client-assertion-validation/src/types.ts b/packages/client-assertion-validation/src/types.ts index a59abc6ee9..c6a882c626 100644 --- a/packages/client-assertion-validation/src/types.ts +++ b/packages/client-assertion-validation/src/types.ts @@ -83,10 +83,9 @@ export type ApiKey = z.infer; export type ValidationResult = SuccessfulValidation | FailedValidation; -export type SuccessfulValidation = { errors: undefined; data: T }; +export type SuccessfulValidation = { data: T }; export type FailedValidation = { errors: Array>; - data: undefined; }; export const ClientAssertionValidationRequest = z.object({ diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index ae9fdccf16..19d6863193 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -194,22 +194,10 @@ export const successfulValidation = ( result: T ): SuccessfulValidation => ({ data: result, - errors: undefined, }); export const failedValidation = ( - // errors: [[error1, error2, undefined], error3, undefined] - errors: Array< - Array | undefined> | ApiError | undefined - > -): FailedValidation => { - const nestedArrayWithoutUndefined = errors.filter((a) => a !== undefined); - const flattenedArray = nestedArrayWithoutUndefined.flat(1); - const flattenedArrayWithoutUndefined = flattenedArray.filter( - (e) => e !== undefined - ); - return { - data: undefined, - errors: flattenedArrayWithoutUndefined as Array>, - }; -}; + errors: Array | undefined> +): FailedValidation => ({ + errors: errors.filter((v): v is NonNullable => Boolean(v)), +}); diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index 2ab35847c1..bdf5c9ae56 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -106,6 +106,34 @@ export const validateRequestParameters = ( return failedValidation([assertionTypeError, grantTypeError]); }; +type ValidationOutput = { + jti: ValidationResult; + iat: ValidationResult; +}; + +function isValidationSuccess( + validationResult: ValidationResult +): validationResult is Extract { + return "data" in validationResult && Boolean(validationResult.data); +} + +function isValidationError( + validationResult: ValidationResult +): validationResult is Extract { + return "errors" in validationResult && Array.isArray(validationResult.errors); +} + +function isAllValidationSuccess( + validationOutput: ValidationOutput +): validationOutput is { + [TKey in keyof typeof validationOutput]: Extract< + (typeof validationOutput)[TKey], + { data: unknown } + >; +} { + return Object.values(validationOutput).every(isValidationSuccess); +} + // eslint-disable-next-line complexity export const verifyClientAssertion = ( clientAssertionJws: string, @@ -121,48 +149,14 @@ export const verifyClientAssertion = ( return failedValidation([unexpectedClientAssertionPayload()]); } - const { errors: jtiErrors, data: validatedJti } = validateJti( - decoded.payload.jti - ); - const { errors: iatErrors, data: validatedIat } = validateIat( - decoded.payload.iat - ); - const { errors: expErrors, data: validatedExp } = validateExp( - decoded.payload.exp - ); - const { errors: issErrors, data: validatedIss } = validateIss( - decoded.payload.iss - ); - const { errors: subErrors, data: validatedSub } = validateSub( - decoded.payload.sub, - clientId - ); - const { errors: purposeIdErrors, data: validatedPurposeId } = - validatePurposeId(decoded.payload.purposeId); - const { errors: kidErrors, data: validatedKid } = validateKid( - decoded.header.kid - ); - const { errors: audErrors, data: validatedAud } = validateAudience( - decoded.payload.aud - ); - const { errors: algErrors, data: validatedAlg } = validateAlgorithm( - decoded.header.alg - ); - const { errors: digestErrors, data: validatedDigest } = validateDigest( - decoded.payload.digest - ); - if ( - !jtiErrors && - !iatErrors && - !expErrors && - !issErrors && - !subErrors && - !purposeIdErrors && - !kidErrors && - !audErrors && - !algErrors && - !digestErrors - ) { + const validationOutput: ValidationOutput = { + jti: validateJti(decoded.payload.jti), + iat: validateIat(decoded.payload.iat), + }; + + if (isAllValidationSuccess(validationOutput)) { + // build client assertion + const result: ClientAssertion = { header: { kid: validatedKid, @@ -171,8 +165,8 @@ export const verifyClientAssertion = ( payload: { sub: validatedSub, purposeId: validatedPurposeId, - jti: validatedJti, - iat: validatedIat, + jti: validationOutput.jti.data, + iat: validationOutput.iat.data, iss: validatedIss, aud: validatedAud, exp: validatedExp, @@ -181,18 +175,11 @@ export const verifyClientAssertion = ( }; return successfulValidation(result); } - return failedValidation([ - jtiErrors, - iatErrors, - expErrors, - issErrors, - subErrors, - purposeIdErrors, - kidErrors, - audErrors, - algErrors, - digestErrors, - ]); + + const errors = Object.values(validationOutput) + .filter(isValidationError) + .flatMap(({ errors }) => errors); + return failedValidation(errors); } catch (error) { return failedValidation([unexpectedClientAssertionPayload()]); } @@ -240,9 +227,14 @@ export const validateClientKindAndPlatformState = ( ) .with(clientKindTokenStates.consumer, () => { if (ConsumerKey.safeParse(key).success) { - const { errors: platformStateErrors } = validatePlatformState( - key as ConsumerKey - ); + const validationResult = validatePlatformState(key as ConsumerKey); + + if (isValidationSuccess(validationResult)) { + // ... + } else { + validationResult.errors; + } + const purposeIdError = jwt.payload.purposeId ? undefined : purposeIdNotProvided(); @@ -257,3 +249,64 @@ export const validateClientKindAndPlatformState = ( ]); }) .exhaustive(); + +// const { errors: jtiErrors, data: validatedJti } = validateJti( +// decoded.payload.jti +// ); +// const { errors: iatErrors, data: validatedIat } = validateIat( +// decoded.payload.iat +// ); +// const { errors: expErrors, data: validatedExp } = validateExp( +// decoded.payload.exp +// ); +// const { errors: issErrors, data: validatedIss } = validateIss( +// decoded.payload.iss +// ); +// const { errors: subErrors, data: validatedSub } = validateSub( +// decoded.payload.sub, +// clientId +// ); +// const { errors: purposeIdErrors, data: validatedPurposeId } = +// validatePurposeId(decoded.payload.purposeId); +// const { errors: kidErrors, data: validatedKid } = validateKid( +// decoded.header.kid +// ); +// const { errors: audErrors, data: validatedAud } = validateAudience( +// decoded.payload.aud +// ); +// const { errors: algErrors, data: validatedAlg } = validateAlgorithm( +// decoded.header.alg +// ); +// const { errors: digestErrors, data: validatedDigest } = validateDigest( +// decoded.payload.digest +// ); +// if ( +// !jtiErrors && +// !iatErrors && +// !expErrors && +// !issErrors && +// !subErrors && +// !purposeIdErrors && +// !kidErrors && +// !audErrors && +// !algErrors && +// !digestErrors +// ) { +// const result: ClientAssertion = { +// header: { +// kid: validatedKid, +// alg: validatedAlg, +// }, +// payload: { +// sub: validatedSub, +// purposeId: validatedPurposeId, +// jti: validatedJti, +// iat: validatedIat, +// iss: validatedIss, +// aud: validatedAud, +// exp: validatedExp, +// digest: validatedDigest, +// }, +// }; +// return successfulValidation(result); +// } From c1c214e11ad0b99941c4be3b8e7abd8e95d66b92 Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 25 Sep 2024 15:07:43 +0200 Subject: [PATCH 234/241] Reapply "Fix types" This reverts commit 1bbe00c8b65d22eb96f0dcb18f464271e0af6cd9. --- .../client-assertion-validation/src/validation.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index bdf5c9ae56..890992a84c 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -30,7 +30,9 @@ import { ClientAssertion, ClientAssertionValidationRequest, ConsumerKey, + FailedValidation, Key, + SuccessfulValidation, ValidationResult, } from "./types.js"; import { @@ -113,23 +115,20 @@ type ValidationOutput = { function isValidationSuccess( validationResult: ValidationResult -): validationResult is Extract { - return "data" in validationResult && Boolean(validationResult.data); +): validationResult is SuccessfulValidation { + return "data" in validationResult && validationResult.data !== undefined; } function isValidationError( validationResult: ValidationResult -): validationResult is Extract { +): validationResult is FailedValidation { return "errors" in validationResult && Array.isArray(validationResult.errors); } function isAllValidationSuccess( validationOutput: ValidationOutput ): validationOutput is { - [TKey in keyof typeof validationOutput]: Extract< - (typeof validationOutput)[TKey], - { data: unknown } - >; + [TKey in keyof ValidationOutput]: SuccessfulValidation; } { return Object.values(validationOutput).every(isValidationSuccess); } @@ -230,6 +229,7 @@ export const validateClientKindAndPlatformState = ( const validationResult = validatePlatformState(key as ConsumerKey); if (isValidationSuccess(validationResult)) { + validationResult.data; // ... } else { validationResult.errors; From 22d90e1dd7c86fbb8a1234e15c9e2ea5b0133e8c Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 25 Sep 2024 15:18:23 +0200 Subject: [PATCH 235/241] Draft --- .../client-assertion-validation/src/utils.ts | 3 + .../src/validation.ts | 90 +++++++++++++------ 2 files changed, 67 insertions(+), 26 deletions(-) diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index 19d6863193..360e21b5e6 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -74,6 +74,7 @@ export const validateIss = (iss?: string): ValidationResult => { }; export const validateSub = ( + // TODO requires refactor sub?: string, clientId?: string ): ValidationResult => { @@ -148,6 +149,7 @@ export const validateAlgorithm = (alg?: string): ValidationResult => { }; export const validateDigest = ( + // TODO requires refactor digest?: object ): ValidationResult => { if (!digest) { @@ -173,6 +175,7 @@ export const validateDigest = ( }; export const validatePlatformState = ( + // TODO requires refactor key: ConsumerKey ): ValidationResult => { const agreementError = diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index 890992a84c..9d74bb66d0 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -7,7 +7,7 @@ import { verify, } from "jsonwebtoken"; import { match } from "ts-pattern"; -import { clientKindTokenStates } from "pagopa-interop-models"; +import { clientKindTokenStates, PurposeId } from "pagopa-interop-models"; import { failedValidation, successfulValidation, @@ -28,6 +28,7 @@ import { import { ApiKey, ClientAssertion, + ClientAssertionDigest, ClientAssertionValidationRequest, ConsumerKey, FailedValidation, @@ -108,9 +109,17 @@ export const validateRequestParameters = ( return failedValidation([assertionTypeError, grantTypeError]); }; -type ValidationOutput = { +type VerifyClientAssertion_ValidationOutput = { + kid: ValidationResult; + alg: ValidationResult; + sub: ValidationResult; + purposeId: ValidationResult; jti: ValidationResult; iat: ValidationResult; + iss: ValidationResult; + aud: ValidationResult; + exp: ValidationResult; + digest: ValidationResult; }; function isValidationSuccess( @@ -126,7 +135,18 @@ function isValidationError( } function isAllValidationSuccess( - validationOutput: ValidationOutput + validationOutput: VerifyClientAssertion_ValidationOutput +): validationOutput is { + [TKey in keyof typeof validationOutput]: Extract< + (typeof validationOutput)[TKey], + { data: unknown } + >; +} { + return Object.values(validationOutput).every(isValidationSuccess); +} + +function isAllValidationSuccess2( + validationOutput: ValidateClientKindAndPlatformState_ValidationOutput ): validationOutput is { [TKey in keyof ValidationOutput]: SuccessfulValidation; } { @@ -148,9 +168,17 @@ export const verifyClientAssertion = ( return failedValidation([unexpectedClientAssertionPayload()]); } - const validationOutput: ValidationOutput = { + const validationOutput: VerifyClientAssertion_ValidationOutput = { + kid: validateKid(decoded.header.kid), + alg: validateAlgorithm(decoded.header.alg), + sub: validateSub(decoded.payload.sub, clientId), + purposeId: validatePurposeId(decoded.payload.purposeId), jti: validateJti(decoded.payload.jti), iat: validateIat(decoded.payload.iat), + iss: validateIss(decoded.payload.iss), + aud: validateAudience(decoded.payload.aud), + exp: validateExp(decoded.payload.exp), + digest: validateDigest(decoded.payload.digest), }; if (isAllValidationSuccess(validationOutput)) { @@ -158,18 +186,18 @@ export const verifyClientAssertion = ( const result: ClientAssertion = { header: { - kid: validatedKid, - alg: validatedAlg, + kid: validationOutput.kid.data, + alg: validationOutput.alg.data, }, payload: { - sub: validatedSub, - purposeId: validatedPurposeId, + sub: validationOutput.sub.data, + purposeId: validationOutput.purposeId.data, jti: validationOutput.jti.data, iat: validationOutput.iat.data, - iss: validatedIss, - aud: validatedAud, - exp: validatedExp, - digest: validatedDigest, + iss: validationOutput.iss.data, + aud: validationOutput.aud.data, + exp: validationOutput.exp.data, + digest: validationOutput.digest.data, }, }; return successfulValidation(result); @@ -214,6 +242,20 @@ export const verifyClientAssertionSignature = ( } }; +type ValidateClientKindAndPlatformState_ValidationOutput = { + consumerKey: ValidationResult; + purposeId: ValidationResult; +}; + +const validateRequiredPurposeId = ( + purposeId: PurposeId | undefined +): ValidationResult => { + if (purposeId) { + return successfulValidation(purposeId); + } + return failedValidation([purposeIdNotProvided()]); +}; + export const validateClientKindAndPlatformState = ( key: ApiKey | ConsumerKey, jwt: ClientAssertion @@ -226,23 +268,19 @@ export const validateClientKindAndPlatformState = ( ) .with(clientKindTokenStates.consumer, () => { if (ConsumerKey.safeParse(key).success) { - const validationResult = validatePlatformState(key as ConsumerKey); + const res: ValidateClientKindAndPlatformState_ValidationOutput = { + consumerKey: validatePlatformState(key as ConsumerKey), + purposeId: validateRequiredPurposeId(jwt.payload.purposeId), + }; - if (isValidationSuccess(validationResult)) { - validationResult.data; - // ... - } else { - validationResult.errors; - } - - const purposeIdError = jwt.payload.purposeId - ? undefined - : purposeIdNotProvided(); - - if (!platformStateErrors && !purposeIdError) { + if (isAllValidationSuccess2(res)) { return successfulValidation(jwt); + } else { + const errors = Object.values(res) + .filter(isValidationError) + .flatMap(({ errors }) => errors); + return failedValidation(errors); } - return failedValidation([platformStateErrors, purposeIdError]); } return failedValidation([ unexpectedKeyType(clientKindTokenStates.consumer), From dde897a44dcedfb81e5fae205a94e9bd55ed599a Mon Sep 17 00:00:00 2001 From: Carmine Porricelli Date: Wed, 25 Sep 2024 16:08:52 +0200 Subject: [PATCH 236/241] wip --- .../client-assertion-validation/src/types.ts | 122 +++++++---- .../client-assertion-validation/src/utils.ts | 191 +++++++++--------- .../src/validation.ts | 110 ++++------ 3 files changed, 225 insertions(+), 198 deletions(-) diff --git a/packages/client-assertion-validation/src/types.ts b/packages/client-assertion-validation/src/types.ts index c6a882c626..b82f648ae1 100644 --- a/packages/client-assertion-validation/src/types.ts +++ b/packages/client-assertion-validation/src/types.ts @@ -15,43 +15,97 @@ import { EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE, } from "./utils.js"; -export const ClientAssertionDigest = z - .object({ - alg: z.string(), - value: z.string(), - }) - .strict(); -export type ClientAssertionDigest = z.infer; +// export const ClientAssertionDigest = z +// .object({ +// alg: z.string(), +// value: z.string(), +// }) +// .strict(); +// export type ClientAssertionDigest = z.infer; -export const ClientAssertionHeader = z - .object({ - kid: z.string(), - alg: z.string(), - }) - .strict(); -export type ClientAssertionHeader = z.infer; +// export const ClientAssertionHeader = z +// .object({ +// kid: z.string(), +// alg: z.string(), +// }) +// .strict(); +// export type ClientAssertionHeader = z.infer; -export const ClientAssertionPayload = z - .object({ - sub: z.string(), - jti: z.string(), - iat: z.number(), - iss: z.string(), - aud: z.array(z.string()), - exp: z.number(), - digest: ClientAssertionDigest, - purposeId: PurposeId.optional(), - }) - .strict(); -export type ClientAssertionPayload = z.infer; +// export const ClientAssertionPayload = z +// .object({ +// sub: z.string(), +// jti: z.string(), +// iat: z.number(), +// iss: z.string(), +// aud: z.array(z.string()), +// exp: z.number(), +// digest: ClientAssertionDigest, +// purposeId: PurposeId.optional(), +// }) +// .strict(); +// export type ClientAssertionPayload = z.infer; -export const ClientAssertion = z - .object({ - header: ClientAssertionHeader, - payload: ClientAssertionPayload, - }) - .strict(); -export type ClientAssertion = z.infer; +// export const ClientAssertion = z +// .object({ +// header: ClientAssertionHeader, +// payload: ClientAssertionPayload, +// }) +// .strict(); +// export type ClientAssertion = z.infer; + +declare const brand: unique symbol; + +export type Brand = T & { [brand]: TBrand }; + +export type ValidatedAlg = Brand; +export type ValidatedKid = Brand; +export type ValidatedSub = Brand; +export type ValidatedJti = Brand; +export type ValidatedIat = Brand; +export type ValidatedIss = Brand; +export type ValidatedAud = Brand; +export type ValidatedExp = Brand; +export type ValidatedPurposeId = Brand; +export type ValidatedDigestValue = Brand; +export type ValidatedDigest = Brand< + { + alg: ValidatedAlg; + value: ValidatedDigestValue; + }, + "digest" +>; +export type ClientAssertionValidationResult = { + alg: ValidationResult_; + kid: ValidationResult_; + sub: ValidationResult_; + jti: ValidationResult_; + iat: ValidationResult_; + iss: ValidationResult_; + aud: ValidationResult_; + exp: ValidationResult_; + digest: ValidationResult_; + purposeId?: ValidationResult_; +}; + +export type ValidatedClientAssertion = { + header: { + alg: ValidatedAlg; + kid: ValidatedKid; + }; + payload: { + sub: ValidatedSub; + jti: ValidatedJti; + iat: ValidatedIat; + iss: ValidatedIss; + aud: ValidatedAud; + exp: ValidatedExp; + digest: ValidatedDigest; + purposeId?: ValidatedPurposeId; + }; +}; + +export type ValidationErrors = { _errors: Array> }; +export type ValidationResult_ = T | ValidationErrors; export const Key = z .object({ diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index 360e21b5e6..d963eefc2b 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -1,19 +1,21 @@ +/* eslint-disable no-underscore-dangle */ +/* eslint-disable functional/immutable-data */ +import { ClientId, PurposeId, unsafeBrandId } from "pagopa-interop-models"; import { - ApiError, - ClientId, - itemState, - PurposeId, - unsafeBrandId, -} from "pagopa-interop-models"; -import { - ClientAssertionDigest, - ConsumerKey, - FailedValidation, - ValidationResult, - SuccessfulValidation, + ValidationResult_, + ValidatedKid, + ValidatedAlg, + ValidatedDigest, + ValidationErrors, + ValidatedPurposeId, + ValidatedSub, + ValidatedIss, + ValidatedExp, + ValidatedJti, + ValidatedIat, + ValidatedAud, } from "./types.js"; import { - ErrorCodes, expNotFound, issuedAtNotFound, invalidAudience, @@ -45,162 +47,161 @@ export const EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE = "client_credentials"; // T export const ALLOWED_ALGORITHM = "RS256"; const ALLOWED_DIGEST_ALGORITHM = "SHA256"; -export const validateJti = (jti?: string): ValidationResult => { +export const validateJti = (jti?: string): ValidationResult_ => { if (!jti) { - return failedValidation([jtiNotFound()]); + return { _errors: [jtiNotFound()] }; } - return successfulValidation(jti); + return jti as ValidatedJti; }; -export const validateIat = (iat?: number): ValidationResult => { +export const validateIat = (iat?: number): ValidationResult_ => { if (!iat) { - return failedValidation([issuedAtNotFound()]); + return { _errors: [issuedAtNotFound()] }; } - return successfulValidation(iat); + return iat as ValidatedIat; }; -export const validateExp = (exp?: number): ValidationResult => { +export const validateExp = (exp?: number): ValidationResult_ => { if (!exp) { - return failedValidation([expNotFound()]); + return { _errors: [expNotFound()] }; } - return successfulValidation(exp); + return exp as ValidatedExp; }; -export const validateIss = (iss?: string): ValidationResult => { +export const validateIss = (iss?: string): ValidationResult_ => { if (!iss) { - return failedValidation([issuerNotFound()]); + return { _errors: [issuerNotFound()] }; } - return successfulValidation(iss); + return iss as ValidatedIss; }; export const validateSub = ( // TODO requires refactor sub?: string, clientId?: string -): ValidationResult => { +): ValidationResult_ => { if (!sub) { - return failedValidation([subjectNotFound()]); + return { _errors: [subjectNotFound()] }; } if (clientId) { - const clientIdError = !ClientId.safeParse(clientId).success - ? invalidClientIdFormat(clientId) - : undefined; - const invalidSubFormatError = !ClientId.safeParse(sub).success - ? invalidSubjectFormat(sub) - : undefined; - if (clientIdError || invalidSubFormatError) { - return failedValidation([clientIdError, invalidSubFormatError]); + const validationErrors: ValidationErrors = { _errors: [] }; + + if (!ClientId.safeParse(clientId).success) { + validationErrors._errors.push(invalidClientIdFormat(clientId)); + } + + if (!ClientId.safeParse(sub).success) { + validationErrors._errors.push(invalidSubjectFormat(sub)); + } + + if (validationErrors._errors.length > 0) { + return validationErrors; } + // TODO: clientId undefined OK? if (sub !== clientId) { - return failedValidation([invalidSubject(sub)]); + return { _errors: [invalidSubject(sub)] }; } } - return successfulValidation(sub); + return sub as ValidatedSub; }; export const validatePurposeId = ( purposeId?: string -): ValidationResult => { +): ValidationResult_ => { if (purposeId && !PurposeId.safeParse(purposeId).success) { - return failedValidation([invalidPurposeIdClaimFormat(purposeId)]); + return { _errors: [invalidPurposeIdClaimFormat(purposeId)] }; } const validatedPurposeId = purposeId ? unsafeBrandId(purposeId) : undefined; - return successfulValidation(validatedPurposeId); + + return validatedPurposeId as ValidatedPurposeId | undefined; }; -export const validateKid = (kid?: string): ValidationResult => { +export const validateKid = (kid?: string): ValidationResult_ => { if (!kid) { - return failedValidation([kidNotFound()]); + return { _errors: [kidNotFound()] }; } const alphanumericRegex = new RegExp("^[a-zA-Z0-9]+$"); if (alphanumericRegex.test(kid)) { - return successfulValidation(kid); + return kid as ValidatedKid; } - return failedValidation([invalidKidFormat()]); + return { _errors: [invalidKidFormat()] }; }; export const validateAudience = ( aud: string | string[] | undefined -): ValidationResult => { +): ValidationResult_ => { if (aud === config.clientAssertionAudience) { - return successfulValidation([aud]); + return [aud] as ValidatedAud; } if (!Array.isArray(aud)) { - return failedValidation([invalidAudienceFormat()]); + return { _errors: [invalidAudienceFormat()] }; } if (!aud.includes(config.clientAssertionAudience)) { - return failedValidation([invalidAudience()]); + return { _errors: [invalidAudience()] }; } - return successfulValidation(aud); + return aud as ValidatedAud; }; -export const validateAlgorithm = (alg?: string): ValidationResult => { +export const validateAlgorithm = ( + alg?: string +): ValidationResult_ => { if (!alg) { - return failedValidation([algorithmNotFound()]); + return { _errors: [algorithmNotFound()] }; } if (alg === ALLOWED_ALGORITHM) { - return successfulValidation(alg); + return alg as ValidatedAlg; } - return failedValidation([algorithmNotAllowed(alg)]); + return { _errors: [algorithmNotAllowed(alg)] }; }; export const validateDigest = ( - // TODO requires refactor digest?: object -): ValidationResult => { +): ValidationResult_ => { if (!digest) { - return failedValidation([digestClaimNotFound()]); + return { _errors: [digestClaimNotFound()] }; } const result = ClientAssertionDigest.safeParse(digest); if (!result.success) { - return failedValidation([invalidDigestFormat()]); + return { _errors: [invalidDigestFormat()] }; } - const validatedDigest = result.data; - const digestLengthError = - validatedDigest.value.length !== 64 - ? invalidHashLength(validatedDigest.alg) - : undefined; - const digestAlgError = - validatedDigest.alg !== ALLOWED_DIGEST_ALGORITHM - ? invalidHashAlgorithm() - : undefined; - if (!digestLengthError && !digestAlgError) { - return successfulValidation(result.data); - } - return failedValidation([digestLengthError, digestAlgError]); -}; -export const validatePlatformState = ( - // TODO requires refactor - key: ConsumerKey -): ValidationResult => { - const agreementError = - key.agreementState !== itemState.active ? inactiveAgreement() : undefined; + const validatedDigest = result.data; + const validationErrors: ValidationErrors = { _errors: [] }; - const descriptorError = - key.descriptorState !== itemState.active ? inactiveEService() : undefined; + if (validatedDigest.value.length !== 64) { + validationErrors._errors.push(invalidHashLength(validatedDigest.alg)); + } - const purposeError = - key.purposeState !== itemState.active ? inactivePurpose() : undefined; + if (validatedDigest.alg !== ALLOWED_DIGEST_ALGORITHM) { + validationErrors._errors.push(invalidHashAlgorithm()); + } - if (!agreementError && !descriptorError && !purposeError) { - return successfulValidation(key); + if (validationErrors._errors.length > 0) { + return validationErrors; } - return failedValidation([agreementError, descriptorError, purposeError]); + + return result.data as ValidatedDigest; }; -export const successfulValidation = ( - result: T -): SuccessfulValidation => ({ - data: result, -}); - -export const failedValidation = ( - errors: Array | undefined> -): FailedValidation => ({ - errors: errors.filter((v): v is NonNullable => Boolean(v)), -}); +// export const validatePlatformState = ( +// // TODO requires refactor +// key: ConsumerKey +// ): ValidationResult => { +// const agreementError = +// key.agreementState !== itemState.active ? inactiveAgreement() : undefined; + +// const descriptorError = +// key.descriptorState !== itemState.active ? inactiveEService() : undefined; + +// const purposeError = +// key.purposeState !== itemState.active ? inactivePurpose() : undefined; + +// if (!agreementError && !descriptorError && !purposeError) { +// return successfulValidation(key); +// } +// return failedValidation([agreementError, descriptorError, purposeError]); +// }; diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index 9d74bb66d0..61cc2f1df4 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -1,13 +1,19 @@ +/* eslint-disable no-underscore-dangle */ import { decode, JsonWebTokenError, + Jwt, JwtPayload, NotBeforeError, TokenExpiredError, verify, } from "jsonwebtoken"; import { match } from "ts-pattern"; -import { clientKindTokenStates, PurposeId } from "pagopa-interop-models"; +import { + ApiError, + clientKindTokenStates, + PurposeId, +} from "pagopa-interop-models"; import { failedValidation, successfulValidation, @@ -30,14 +36,17 @@ import { ClientAssertion, ClientAssertionDigest, ClientAssertionValidationRequest, + ClientAssertionValidationResult, ConsumerKey, FailedValidation, Key, SuccessfulValidation, + ValidatedClientAssertion, ValidationResult, } from "./types.js"; import { clientAssertionSignatureVerificationFailure, + ErrorCodes, invalidAssertionType, invalidClientAssertionFormat, invalidClientAssertionSignatureType, @@ -109,66 +118,35 @@ export const validateRequestParameters = ( return failedValidation([assertionTypeError, grantTypeError]); }; -type VerifyClientAssertion_ValidationOutput = { - kid: ValidationResult; - alg: ValidationResult; - sub: ValidationResult; - purposeId: ValidationResult; - jti: ValidationResult; - iat: ValidationResult; - iss: ValidationResult; - aud: ValidationResult; - exp: ValidationResult; - digest: ValidationResult; -}; - -function isValidationSuccess( - validationResult: ValidationResult -): validationResult is SuccessfulValidation { - return "data" in validationResult && validationResult.data !== undefined; -} - -function isValidationError( - validationResult: ValidationResult -): validationResult is FailedValidation { - return "errors" in validationResult && Array.isArray(validationResult.errors); -} - -function isAllValidationSuccess( - validationOutput: VerifyClientAssertion_ValidationOutput -): validationOutput is { - [TKey in keyof typeof validationOutput]: Extract< - (typeof validationOutput)[TKey], - { data: unknown } +function hasValidationFailed( + caValidationResult: ClientAssertionValidationResult +): caValidationResult is { + [TKey in keyof ClientAssertionValidationResult]: Extract< + ClientAssertionValidationResult[TKey], + { _errors: unknown[] } >; } { - return Object.values(validationOutput).every(isValidationSuccess); -} - -function isAllValidationSuccess2( - validationOutput: ValidateClientKindAndPlatformState_ValidationOutput -): validationOutput is { - [TKey in keyof ValidationOutput]: SuccessfulValidation; -} { - return Object.values(validationOutput).every(isValidationSuccess); + return Object.values(caValidationResult).some( + (result) => "_errors" in result && Array.isArray(result._errors) + ); } // eslint-disable-next-line complexity export const verifyClientAssertion = ( clientAssertionJws: string, clientId: string | undefined -): ValidationResult => { +): Array> | ValidatedClientAssertion => { try { const decoded = decode(clientAssertionJws, { complete: true, json: true }); if (!decoded) { - return failedValidation([invalidClientAssertionFormat()]); + return [invalidClientAssertionFormat()]; } if (typeof decoded.payload === "string") { - return failedValidation([unexpectedClientAssertionPayload()]); + return [unexpectedClientAssertionPayload()]; } - const validationOutput: VerifyClientAssertion_ValidationOutput = { + const validationOutput: ClientAssertionValidationResult = { kid: validateKid(decoded.header.kid), alg: validateAlgorithm(decoded.header.alg), sub: validateSub(decoded.payload.sub, clientId), @@ -181,32 +159,26 @@ export const verifyClientAssertion = ( digest: validateDigest(decoded.payload.digest), }; - if (isAllValidationSuccess(validationOutput)) { - // build client assertion - - const result: ClientAssertion = { - header: { - kid: validationOutput.kid.data, - alg: validationOutput.alg.data, - }, - payload: { - sub: validationOutput.sub.data, - purposeId: validationOutput.purposeId.data, - jti: validationOutput.jti.data, - iat: validationOutput.iat.data, - iss: validationOutput.iss.data, - aud: validationOutput.aud.data, - exp: validationOutput.exp.data, - digest: validationOutput.digest.data, - }, - }; - return successfulValidation(result); + if (hasValidationFailed(validationOutput)) { + return Object.values(validationOutput).flatMap(({ _errors }) => _errors); } - const errors = Object.values(validationOutput) - .filter(isValidationError) - .flatMap(({ errors }) => errors); - return failedValidation(errors); + return { + header: { + kid: validationOutput.kid, + alg: validationOutput.alg, + }, + payload: { + sub: validationOutput.sub, + purposeId: validationOutput.purposeId, + jti: validationOutput.jti, + iat: validationOutput.iat, + iss: validationOutput.iss, + aud: validationOutput.aud, + exp: validationOutput.exp, + digest: validationOutput.digest, + }, + }; } catch (error) { return failedValidation([unexpectedClientAssertionPayload()]); } From 905aa064a28dbae16ec3b9a1d67ea8789aa94e01 Mon Sep 17 00:00:00 2001 From: Stefano Hu <76391491+shuyec@users.noreply.github.com> Date: Wed, 25 Sep 2024 16:30:59 +0200 Subject: [PATCH 237/241] Fix agreementDescriptorId type --- .../src/token-generation-readmodel/platform-states-entry.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/models/src/token-generation-readmodel/platform-states-entry.ts b/packages/models/src/token-generation-readmodel/platform-states-entry.ts index e2be636935..fddfc302a8 100644 --- a/packages/models/src/token-generation-readmodel/platform-states-entry.ts +++ b/packages/models/src/token-generation-readmodel/platform-states-entry.ts @@ -1,5 +1,6 @@ import { z } from "zod"; import { + DescriptorId, EServiceId, PurposeId, PurposeVersionId, @@ -53,7 +54,7 @@ export const PlatformStatesAgreementEntry = PlatformStatesBaseEntry.extend({ PK: PlatformStatesAgreementPK, GSIPK_consumerId_eserviceId: GSIPKConsumerIdEServiceId, GSISK_agreementTimestamp: z.string().datetime(), - agreementDescriptorId: z.string(), + agreementDescriptorId: DescriptorId, }); export type PlatformStatesAgreementEntry = z.infer< typeof PlatformStatesAgreementEntry From c3f442d0ad6bfd87bdfab0a8a962bd74ffc221ff Mon Sep 17 00:00:00 2001 From: Roberto Taglioni Date: Wed, 25 Sep 2024 16:54:47 +0200 Subject: [PATCH 238/241] Update tables setup --- .../commons-test/src/setupDynamoDBtables.ts | 85 +++++++++++++++++-- 1 file changed, 80 insertions(+), 5 deletions(-) diff --git a/packages/commons-test/src/setupDynamoDBtables.ts b/packages/commons-test/src/setupDynamoDBtables.ts index a8ed5e8291..bf749dd6f9 100644 --- a/packages/commons-test/src/setupDynamoDBtables.ts +++ b/packages/commons-test/src/setupDynamoDBtables.ts @@ -12,9 +12,36 @@ export const buildDynamoDBTables = async ( const platformTableDefinition: CreateTableInput = { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion TableName: "platform-states", - AttributeDefinitions: [{ AttributeName: "PK", AttributeType: "S" }], + AttributeDefinitions: [ + { AttributeName: "PK", AttributeType: "S" }, + { AttributeName: "GSIPK_consumerId_eserviceId", AttributeType: "S" }, + { AttributeName: "GSISK_agreementTimestamp", AttributeType: "S" }, + ], KeySchema: [{ AttributeName: "PK", KeyType: "HASH" }], BillingMode: "PAY_PER_REQUEST", + GlobalSecondaryIndexes: [ + { + IndexName: "GSIPK_consumerId_eserviceId", + KeySchema: [ + { + AttributeName: "GSIPK_consumerId_eserviceId", + KeyType: "HASH", + }, + { + AttributeName: "GSISK_agreementTimestamp", + KeyType: "RANGE", + }, + ], + Projection: { + NonKeyAttributes: [], + ProjectionType: "ALL", + }, + // ProvisionedThroughput: { + // ReadCapacityUnits: 5, + // WriteCapacityUnits: 5, + // }, + }, + ], }; const command1 = new CreateTableCommand(platformTableDefinition); await dynamoDBClient.send(command1); @@ -25,6 +52,11 @@ export const buildDynamoDBTables = async ( AttributeDefinitions: [ { AttributeName: "PK", AttributeType: "S" }, { AttributeName: "GSIPK_eserviceId_descriptorId", AttributeType: "S" }, + { AttributeName: "GSIPK_consumerId_eserviceId", AttributeType: "S" }, + { AttributeName: "GSIPK_purposeId", AttributeType: "S" }, + { AttributeName: "GSIPK_clientId", AttributeType: "S" }, + { AttributeName: "GSIPK_kid", AttributeType: "S" }, + { AttributeName: "GSIPK_clientId_purposeId", AttributeType: "S" }, ], KeySchema: [{ AttributeName: "PK", KeyType: "HASH" }], BillingMode: "PAY_PER_REQUEST", @@ -41,10 +73,53 @@ export const buildDynamoDBTables = async ( NonKeyAttributes: [], ProjectionType: "ALL", }, - // ProvisionedThroughput: { - // ReadCapacityUnits: 5, - // WriteCapacityUnits: 5, - // }, + }, + { + IndexName: "GSIPK_consumerId_eserviceId", + KeySchema: [ + { + AttributeName: "GSIPK_consumerId_eserviceId", + KeyType: "HASH", + }, + ], + Projection: { + NonKeyAttributes: [], + ProjectionType: "ALL", + }, + }, + { + IndexName: "GSIPK_purposeId", + KeySchema: [{ AttributeName: "GSIPK_purposeId", KeyType: "HASH" }], + Projection: { + NonKeyAttributes: [], + ProjectionType: "ALL", + }, + }, + { + IndexName: "GSIPK_clientId", + KeySchema: [{ AttributeName: "GSIPK_clientId", KeyType: "HASH" }], + Projection: { + NonKeyAttributes: [], + ProjectionType: "ALL", + }, + }, + { + IndexName: "GSIPK_kid", + KeySchema: [{ AttributeName: "GSIPK_kid", KeyType: "HASH" }], + Projection: { + NonKeyAttributes: [], + ProjectionType: "ALL", + }, + }, + { + IndexName: "GSIPK_clientId_purposeId", + KeySchema: [ + { AttributeName: "GSIPK_clientId_purposeId", KeyType: "HASH" }, + ], + Projection: { + NonKeyAttributes: [], + ProjectionType: "ALL", + }, }, ], }; From 4e3dcf15e314debefe0a181a777c75b61d29dac3 Mon Sep 17 00:00:00 2001 From: Carmine Porricelli Date: Thu, 26 Sep 2024 11:41:25 +0200 Subject: [PATCH 239/241] Improved typing and logic --- .../client-assertion-validation/src/index.ts | 6 +- .../client-assertion-validation/src/types.ts | 88 ++-- .../client-assertion-validation/src/utils.ts | 161 +++--- .../src/validation.ts | 298 ++++------- .../test/utils.test.ts | 35 +- .../client-assertion-validation/test/utils.ts | 16 + .../test/validation.test.ts | 461 +++++++++--------- 7 files changed, 495 insertions(+), 570 deletions(-) diff --git a/packages/client-assertion-validation/src/index.ts b/packages/client-assertion-validation/src/index.ts index 293a920555..4b42105359 100644 --- a/packages/client-assertion-validation/src/index.ts +++ b/packages/client-assertion-validation/src/index.ts @@ -1,2 +1,6 @@ export * from "./validation.js"; -export * from "./types.js"; +export { + ClientAssertion, + ValidationResult, + ClientAssertionValidationRequest, +} from "./types.js"; diff --git a/packages/client-assertion-validation/src/types.ts b/packages/client-assertion-validation/src/types.ts index b82f648ae1..84e1c58520 100644 --- a/packages/client-assertion-validation/src/types.ts +++ b/packages/client-assertion-validation/src/types.ts @@ -15,43 +15,43 @@ import { EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE, } from "./utils.js"; -// export const ClientAssertionDigest = z -// .object({ -// alg: z.string(), -// value: z.string(), -// }) -// .strict(); -// export type ClientAssertionDigest = z.infer; +export const ClientAssertionDigest = z + .object({ + alg: z.string(), + value: z.string(), + }) + .strict(); +export type ClientAssertionDigest = z.infer; -// export const ClientAssertionHeader = z -// .object({ -// kid: z.string(), -// alg: z.string(), -// }) -// .strict(); -// export type ClientAssertionHeader = z.infer; +export const ClientAssertionHeader = z + .object({ + kid: z.string(), + alg: z.string(), + }) + .strict(); +export type ClientAssertionHeader = z.infer; -// export const ClientAssertionPayload = z -// .object({ -// sub: z.string(), -// jti: z.string(), -// iat: z.number(), -// iss: z.string(), -// aud: z.array(z.string()), -// exp: z.number(), -// digest: ClientAssertionDigest, -// purposeId: PurposeId.optional(), -// }) -// .strict(); -// export type ClientAssertionPayload = z.infer; +export const ClientAssertionPayload = z + .object({ + sub: z.string(), + jti: z.string(), + iat: z.number(), + iss: z.string(), + aud: z.array(z.string()), + exp: z.number(), + digest: ClientAssertionDigest, + purposeId: PurposeId.optional(), + }) + .strict(); +export type ClientAssertionPayload = z.infer; -// export const ClientAssertion = z -// .object({ -// header: ClientAssertionHeader, -// payload: ClientAssertionPayload, -// }) -// .strict(); -// export type ClientAssertion = z.infer; +export const ClientAssertion = z + .object({ + header: ClientAssertionHeader, + payload: ClientAssertionPayload, + }) + .strict(); +export type ClientAssertion = z.infer; declare const brand: unique symbol; @@ -74,18 +74,6 @@ export type ValidatedDigest = Brand< }, "digest" >; -export type ClientAssertionValidationResult = { - alg: ValidationResult_; - kid: ValidationResult_; - sub: ValidationResult_; - jti: ValidationResult_; - iat: ValidationResult_; - iss: ValidationResult_; - aud: ValidationResult_; - exp: ValidationResult_; - digest: ValidationResult_; - purposeId?: ValidationResult_; -}; export type ValidatedClientAssertion = { header: { @@ -100,13 +88,10 @@ export type ValidatedClientAssertion = { aud: ValidatedAud; exp: ValidatedExp; digest: ValidatedDigest; - purposeId?: ValidatedPurposeId; + purposeId: ValidatedPurposeId | undefined; }; }; -export type ValidationErrors = { _errors: Array> }; -export type ValidationResult_ = T | ValidationErrors; - export const Key = z .object({ clientId: ClientId, @@ -137,8 +122,9 @@ export type ApiKey = z.infer; export type ValidationResult = SuccessfulValidation | FailedValidation; -export type SuccessfulValidation = { data: T }; +export type SuccessfulValidation = { hasSucceeded: true; data: T }; export type FailedValidation = { + hasSucceeded: false; errors: Array>; }; diff --git a/packages/client-assertion-validation/src/utils.ts b/packages/client-assertion-validation/src/utils.ts index d963eefc2b..63003d0a3b 100644 --- a/packages/client-assertion-validation/src/utils.ts +++ b/packages/client-assertion-validation/src/utils.ts @@ -1,12 +1,16 @@ /* eslint-disable no-underscore-dangle */ /* eslint-disable functional/immutable-data */ -import { ClientId, PurposeId, unsafeBrandId } from "pagopa-interop-models"; import { - ValidationResult_, + ClientId, + itemState, + PurposeId, + unsafeBrandId, +} from "pagopa-interop-models"; +import { + ValidationResult, ValidatedKid, ValidatedAlg, ValidatedDigest, - ValidationErrors, ValidatedPurposeId, ValidatedSub, ValidatedIss, @@ -14,6 +18,10 @@ import { ValidatedJti, ValidatedIat, ValidatedAud, + SuccessfulValidation, + FailedValidation, + ClientAssertionDigest, + ConsumerKey, } from "./types.js"; import { expNotFound, @@ -26,9 +34,6 @@ import { invalidSubject, invalidPurposeIdClaimFormat, kidNotFound, - inactiveAgreement, - inactiveEService, - inactivePurpose, invalidClientIdFormat, invalidSubjectFormat, algorithmNotFound, @@ -38,6 +43,9 @@ import { invalidHashLength, invalidHashAlgorithm, invalidKidFormat, + inactivePurpose, + inactiveEService, + inactiveAgreement, } from "./errors.js"; import { config } from "./config.js"; @@ -47,161 +55,180 @@ export const EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE = "client_credentials"; // T export const ALLOWED_ALGORITHM = "RS256"; const ALLOWED_DIGEST_ALGORITHM = "SHA256"; -export const validateJti = (jti?: string): ValidationResult_ => { +export const failedValidation = ( + errors: FailedValidation["errors"] +): FailedValidation => ({ + hasSucceeded: false, + errors, +}); + +export const successfulValidation = (data: T): SuccessfulValidation => ({ + hasSucceeded: true, + data, +}); + +export const validateJti = (jti?: string): ValidationResult => { if (!jti) { - return { _errors: [jtiNotFound()] }; + return failedValidation([jtiNotFound()]); } - return jti as ValidatedJti; + return successfulValidation(jti as ValidatedJti); }; -export const validateIat = (iat?: number): ValidationResult_ => { +export const validateIat = (iat?: number): ValidationResult => { if (!iat) { - return { _errors: [issuedAtNotFound()] }; + return failedValidation([issuedAtNotFound()]); } - return iat as ValidatedIat; + return successfulValidation(iat as ValidatedIat); }; -export const validateExp = (exp?: number): ValidationResult_ => { +export const validateExp = (exp?: number): ValidationResult => { if (!exp) { - return { _errors: [expNotFound()] }; + return failedValidation([expNotFound()]); } - return exp as ValidatedExp; + return successfulValidation(exp as ValidatedExp); }; -export const validateIss = (iss?: string): ValidationResult_ => { +export const validateIss = (iss?: string): ValidationResult => { if (!iss) { - return { _errors: [issuerNotFound()] }; + return failedValidation([issuerNotFound()]); } - return iss as ValidatedIss; + return successfulValidation(iss as ValidatedIss); }; export const validateSub = ( // TODO requires refactor sub?: string, clientId?: string -): ValidationResult_ => { +): ValidationResult => { if (!sub) { - return { _errors: [subjectNotFound()] }; + return failedValidation([subjectNotFound()]); } if (clientId) { - const validationErrors: ValidationErrors = { _errors: [] }; + const FailedValidations: FailedValidation["errors"] = []; if (!ClientId.safeParse(clientId).success) { - validationErrors._errors.push(invalidClientIdFormat(clientId)); + FailedValidations.push(invalidClientIdFormat(clientId)); } if (!ClientId.safeParse(sub).success) { - validationErrors._errors.push(invalidSubjectFormat(sub)); + FailedValidations.push(invalidSubjectFormat(sub)); } - if (validationErrors._errors.length > 0) { - return validationErrors; + if (FailedValidations.length > 0) { + return failedValidation(FailedValidations); } // TODO: clientId undefined OK? if (sub !== clientId) { - return { _errors: [invalidSubject(sub)] }; + return failedValidation([invalidSubject(sub)]); } } - return sub as ValidatedSub; + return successfulValidation(sub as ValidatedSub); }; export const validatePurposeId = ( purposeId?: string -): ValidationResult_ => { +): ValidationResult => { if (purposeId && !PurposeId.safeParse(purposeId).success) { - return { _errors: [invalidPurposeIdClaimFormat(purposeId)] }; + return failedValidation([invalidPurposeIdClaimFormat(purposeId)]); } const validatedPurposeId = purposeId ? unsafeBrandId(purposeId) : undefined; - return validatedPurposeId as ValidatedPurposeId | undefined; + return successfulValidation( + validatedPurposeId as ValidatedPurposeId | undefined + ); }; -export const validateKid = (kid?: string): ValidationResult_ => { +export const validateKid = (kid?: string): ValidationResult => { if (!kid) { - return { _errors: [kidNotFound()] }; + return failedValidation([kidNotFound()]); } const alphanumericRegex = new RegExp("^[a-zA-Z0-9]+$"); if (alphanumericRegex.test(kid)) { - return kid as ValidatedKid; + return successfulValidation(kid as ValidatedKid); } - return { _errors: [invalidKidFormat()] }; + return failedValidation([invalidKidFormat()]); }; export const validateAudience = ( aud: string | string[] | undefined -): ValidationResult_ => { +): ValidationResult => { if (aud === config.clientAssertionAudience) { - return [aud] as ValidatedAud; + return successfulValidation([aud] as ValidatedAud); } if (!Array.isArray(aud)) { - return { _errors: [invalidAudienceFormat()] }; + return failedValidation([invalidAudienceFormat()]); } if (!aud.includes(config.clientAssertionAudience)) { - return { _errors: [invalidAudience()] }; + return failedValidation([invalidAudience()]); } - return aud as ValidatedAud; + return successfulValidation(aud as ValidatedAud); }; export const validateAlgorithm = ( alg?: string -): ValidationResult_ => { +): ValidationResult => { if (!alg) { - return { _errors: [algorithmNotFound()] }; + return failedValidation([algorithmNotFound()]); } if (alg === ALLOWED_ALGORITHM) { - return alg as ValidatedAlg; + return successfulValidation(alg as ValidatedAlg); } - return { _errors: [algorithmNotAllowed(alg)] }; + return failedValidation([algorithmNotAllowed(alg)]); }; export const validateDigest = ( digest?: object -): ValidationResult_ => { +): ValidationResult => { if (!digest) { - return { _errors: [digestClaimNotFound()] }; + return failedValidation([digestClaimNotFound()]); } const result = ClientAssertionDigest.safeParse(digest); if (!result.success) { - return { _errors: [invalidDigestFormat()] }; + return failedValidation([invalidDigestFormat()]); } const validatedDigest = result.data; - const validationErrors: ValidationErrors = { _errors: [] }; + const failedValidations: FailedValidation["errors"] = []; if (validatedDigest.value.length !== 64) { - validationErrors._errors.push(invalidHashLength(validatedDigest.alg)); + failedValidations.push(invalidHashLength(validatedDigest.alg)); } if (validatedDigest.alg !== ALLOWED_DIGEST_ALGORITHM) { - validationErrors._errors.push(invalidHashAlgorithm()); + failedValidations.push(invalidHashAlgorithm()); } - if (validationErrors._errors.length > 0) { - return validationErrors; + if (failedValidations.length > 0) { + return failedValidation(failedValidations); } - return result.data as ValidatedDigest; + return successfulValidation(result.data as ValidatedDigest); }; -// export const validatePlatformState = ( -// // TODO requires refactor -// key: ConsumerKey -// ): ValidationResult => { -// const agreementError = -// key.agreementState !== itemState.active ? inactiveAgreement() : undefined; +export const validatePlatformState = ( + // TODO requires refactor + key: ConsumerKey +): ValidationResult => { + const validationErrors: FailedValidation["errors"] = []; + + if (key.agreementState !== itemState.active) { + validationErrors.push(inactiveAgreement()); + } -// const descriptorError = -// key.descriptorState !== itemState.active ? inactiveEService() : undefined; + if (key.descriptorState !== itemState.active) { + validationErrors.push(inactiveEService()); + } -// const purposeError = -// key.purposeState !== itemState.active ? inactivePurpose() : undefined; + if (key.purposeState !== itemState.active) { + validationErrors.push(inactivePurpose()); + } -// if (!agreementError && !descriptorError && !purposeError) { -// return successfulValidation(key); -// } -// return failedValidation([agreementError, descriptorError, purposeError]); -// }; + if (validationErrors.length === 0) { + return successfulValidation(key); + } + return failedValidation(validationErrors); +}; diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index 61cc2f1df4..ecd29614a7 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -1,22 +1,15 @@ -/* eslint-disable no-underscore-dangle */ +/* eslint-disable functional/immutable-data */ import { decode, JsonWebTokenError, - Jwt, JwtPayload, NotBeforeError, TokenExpiredError, verify, } from "jsonwebtoken"; -import { match } from "ts-pattern"; +import { match, P } from "ts-pattern"; +import { clientKindTokenStates, PurposeId } from "pagopa-interop-models"; import { - ApiError, - clientKindTokenStates, - PurposeId, -} from "pagopa-interop-models"; -import { - failedValidation, - successfulValidation, EXPECTED_CLIENT_ASSERTION_TYPE, EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE, validateJti, @@ -29,24 +22,22 @@ import { validateKid, validatePurposeId, validateSub, + successfulValidation, + failedValidation, validatePlatformState, } from "./utils.js"; import { ApiKey, ClientAssertion, - ClientAssertionDigest, ClientAssertionValidationRequest, - ClientAssertionValidationResult, ConsumerKey, - FailedValidation, Key, - SuccessfulValidation, + FailedValidation, ValidatedClientAssertion, ValidationResult, } from "./types.js"; import { clientAssertionSignatureVerificationFailure, - ErrorCodes, invalidAssertionType, invalidClientAssertionFormat, invalidClientAssertionSignatureType, @@ -59,126 +50,98 @@ import { unexpectedKeyType, } from "./errors.js"; -/* -TEMPLATE for client assertion validation - -export const validateClientAssertion = async ( - request: ClientAssertionValidationRequest, -): Promise> => { - const { errors: parametersErrors } = validateRequestParameters(request); - - const { errors: clientAssertionVerificationErrors, data: jwt } = - verifyClientAssertion(request.client_assertion, request.client_id); - - // TO DO retrieve key - - - const { errors: clientAssertionSignatureErrors } = - verifyClientAssertionSignature(request.client_assertion, key); - - if ( - parametersErrors || - clientAssertionVerificationErrors || - clientAssertionSignatureErrors - ) { - return failedValidation([ - parametersErrors, - clientAssertionVerificationErrors, - clientAssertionSignatureErrors, - ]); - } - const { errors: clientKindAndPlatormStateErrors } = - validateClientKindAndPlatformState(key, jwt); - - if (clientKindAndPlatormStateErrors) { - return failedValidation([clientAssertionSignatureErrors]); - } - - return successfulValidation(jwt); -}; -*/ - export const validateRequestParameters = ( request: ClientAssertionValidationRequest ): ValidationResult => { - const assertionTypeError = - request.client_assertion_type !== EXPECTED_CLIENT_ASSERTION_TYPE - ? invalidAssertionType(request.client_assertion_type) - : undefined; + const validationErrors: FailedValidation["errors"] = []; + + if (request.client_assertion_type !== EXPECTED_CLIENT_ASSERTION_TYPE) { + validationErrors.push(invalidAssertionType(request.client_assertion_type)); + } // TODO: this might be useless because ClientAssertionValidationRequest has the string hard coded - const grantTypeError = - request.grant_type !== EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE - ? invalidGrantType(request.grant_type) - : undefined; + if (request.grant_type !== EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE) { + validationErrors.push(invalidGrantType(request.grant_type)); + } - if (!assertionTypeError && !grantTypeError) { + if (validationErrors.length === 0) { return successfulValidation(request); } - return failedValidation([assertionTypeError, grantTypeError]); -}; -function hasValidationFailed( - caValidationResult: ClientAssertionValidationResult -): caValidationResult is { - [TKey in keyof ClientAssertionValidationResult]: Extract< - ClientAssertionValidationResult[TKey], - { _errors: unknown[] } - >; -} { - return Object.values(caValidationResult).some( - (result) => "_errors" in result && Array.isArray(result._errors) - ); -} + return failedValidation(validationErrors); +}; -// eslint-disable-next-line complexity export const verifyClientAssertion = ( clientAssertionJws: string, clientId: string | undefined -): Array> | ValidatedClientAssertion => { +): ValidationResult => { try { const decoded = decode(clientAssertionJws, { complete: true, json: true }); if (!decoded) { - return [invalidClientAssertionFormat()]; + return failedValidation([invalidClientAssertionFormat()]); } if (typeof decoded.payload === "string") { - return [unexpectedClientAssertionPayload()]; + return failedValidation([unexpectedClientAssertionPayload()]); } - const validationOutput: ClientAssertionValidationResult = { - kid: validateKid(decoded.header.kid), - alg: validateAlgorithm(decoded.header.alg), - sub: validateSub(decoded.payload.sub, clientId), - purposeId: validatePurposeId(decoded.payload.purposeId), - jti: validateJti(decoded.payload.jti), - iat: validateIat(decoded.payload.iat), - iss: validateIss(decoded.payload.iss), - aud: validateAudience(decoded.payload.aud), - exp: validateExp(decoded.payload.exp), - digest: validateDigest(decoded.payload.digest), - }; - - if (hasValidationFailed(validationOutput)) { - return Object.values(validationOutput).flatMap(({ _errors }) => _errors); + const kidValidation = validateKid(decoded.header.kid); + const algValidation = validateAlgorithm(decoded.header.alg); + const subValidation = validateSub(decoded.payload.sub, clientId); + const purposeIdValidation = validatePurposeId(decoded.payload.purposeId); + const jtiValidation = validateJti(decoded.payload.jti); + const iatValidation = validateIat(decoded.payload.iat); + const issValidation = validateIss(decoded.payload.iss); + const audValidation = validateAudience(decoded.payload.aud); + const expValidation = validateExp(decoded.payload.exp); + const digestValidation = validateDigest(decoded.payload.digest); + + const errors = [ + kidValidation, + algValidation, + subValidation, + purposeIdValidation, + jtiValidation, + iatValidation, + issValidation, + audValidation, + expValidation, + digestValidation, + ] + .filter((validation) => !validation.hasSucceeded) + .flatMap(({ errors }) => errors); + + if ( + kidValidation.hasSucceeded && + algValidation.hasSucceeded && + subValidation.hasSucceeded && + purposeIdValidation.hasSucceeded && + jtiValidation.hasSucceeded && + iatValidation.hasSucceeded && + issValidation.hasSucceeded && + audValidation.hasSucceeded && + expValidation.hasSucceeded && + digestValidation.hasSucceeded + ) { + return successfulValidation({ + header: { + kid: kidValidation.data, + alg: algValidation.data, + }, + payload: { + sub: subValidation.data, + purposeId: purposeIdValidation.data, + jti: jtiValidation.data, + iat: iatValidation.data, + iss: issValidation.data, + aud: audValidation.data, + exp: expValidation.data, + digest: digestValidation.data, + }, + } satisfies ValidatedClientAssertion); } - return { - header: { - kid: validationOutput.kid, - alg: validationOutput.alg, - }, - payload: { - sub: validationOutput.sub, - purposeId: validationOutput.purposeId, - jti: validationOutput.jti, - iat: validationOutput.iat, - iss: validationOutput.iss, - aud: validationOutput.aud, - exp: validationOutput.exp, - digest: validationOutput.digest, - }, - }; + return failedValidation(errors); } catch (error) { return failedValidation([unexpectedClientAssertionPayload()]); } @@ -201,24 +164,25 @@ export const verifyClientAssertionSignature = ( } return successfulValidation(result); } catch (error: unknown) { - if (error instanceof TokenExpiredError) { - return failedValidation([tokenExpiredError()]); - } else if (error instanceof NotBeforeError) { - return failedValidation([notBeforeError()]); - } else if (error instanceof JsonWebTokenError) { - // TODO: this might overlap with invalidClientAssertionFormat raised inside verifyClientAssertion - return failedValidation([jsonWebTokenError(error.message)]); - } else { - return failedValidation([clientAssertionSignatureVerificationFailure()]); - } + return match(error) + .with(P.instanceOf(TokenExpiredError), () => + failedValidation([tokenExpiredError()]) + ) + .with(P.instanceOf(NotBeforeError), () => + failedValidation([notBeforeError()]) + ) + .with(P.instanceOf(JsonWebTokenError), (error) => + failedValidation([jsonWebTokenError(error.message)]) + ) + .with(P.instanceOf(TokenExpiredError), () => + failedValidation([tokenExpiredError()]) + ) + .otherwise(() => + failedValidation([clientAssertionSignatureVerificationFailure()]) + ); } }; -type ValidateClientKindAndPlatformState_ValidationOutput = { - consumerKey: ValidationResult; - purposeId: ValidationResult; -}; - const validateRequiredPurposeId = ( purposeId: PurposeId | undefined ): ValidationResult => { @@ -240,16 +204,19 @@ export const validateClientKindAndPlatformState = ( ) .with(clientKindTokenStates.consumer, () => { if (ConsumerKey.safeParse(key).success) { - const res: ValidateClientKindAndPlatformState_ValidationOutput = { - consumerKey: validatePlatformState(key as ConsumerKey), - purposeId: validateRequiredPurposeId(jwt.payload.purposeId), - }; - - if (isAllValidationSuccess2(res)) { + const consumerKeyValidation = validatePlatformState(key as ConsumerKey); + const purposeIdValidation = validateRequiredPurposeId( + jwt.payload.purposeId + ); + + if ( + consumerKeyValidation.hasSucceeded && + purposeIdValidation.hasSucceeded + ) { return successfulValidation(jwt); } else { - const errors = Object.values(res) - .filter(isValidationError) + const errors = [consumerKeyValidation, purposeIdValidation] + .filter((validation) => !validation.hasSucceeded) .flatMap(({ errors }) => errors); return failedValidation(errors); } @@ -259,64 +226,3 @@ export const validateClientKindAndPlatformState = ( ]); }) .exhaustive(); - -// const { errors: jtiErrors, data: validatedJti } = validateJti( -// decoded.payload.jti -// ); -// const { errors: iatErrors, data: validatedIat } = validateIat( -// decoded.payload.iat -// ); -// const { errors: expErrors, data: validatedExp } = validateExp( -// decoded.payload.exp -// ); -// const { errors: issErrors, data: validatedIss } = validateIss( -// decoded.payload.iss -// ); -// const { errors: subErrors, data: validatedSub } = validateSub( -// decoded.payload.sub, -// clientId -// ); -// const { errors: purposeIdErrors, data: validatedPurposeId } = -// validatePurposeId(decoded.payload.purposeId); -// const { errors: kidErrors, data: validatedKid } = validateKid( -// decoded.header.kid -// ); -// const { errors: audErrors, data: validatedAud } = validateAudience( -// decoded.payload.aud -// ); -// const { errors: algErrors, data: validatedAlg } = validateAlgorithm( -// decoded.header.alg -// ); -// const { errors: digestErrors, data: validatedDigest } = validateDigest( -// decoded.payload.digest -// ); -// if ( -// !jtiErrors && -// !iatErrors && -// !expErrors && -// !issErrors && -// !subErrors && -// !purposeIdErrors && -// !kidErrors && -// !audErrors && -// !algErrors && -// !digestErrors -// ) { -// const result: ClientAssertion = { -// header: { -// kid: validatedKid, -// alg: validatedAlg, -// }, -// payload: { -// sub: validatedSub, -// purposeId: validatedPurposeId, -// jti: validatedJti, -// iat: validatedIat, -// iss: validatedIss, -// aud: validatedAud, -// exp: validatedExp, -// digest: validatedDigest, -// }, -// }; -// return successfulValidation(result); -// } diff --git a/packages/client-assertion-validation/test/utils.test.ts b/packages/client-assertion-validation/test/utils.test.ts index 522fc73f99..5926439c9f 100644 --- a/packages/client-assertion-validation/test/utils.test.ts +++ b/packages/client-assertion-validation/test/utils.test.ts @@ -7,7 +7,7 @@ describe("failedValidation", () => { const errors = [inactiveEService(), inactiveAgreement()]; const result = failedValidation(errors); expect(result).toEqual({ - data: undefined, + hasSucceeded: false, errors: [inactiveEService(), inactiveAgreement()], }); }); @@ -15,48 +15,21 @@ describe("failedValidation", () => { const errors = [inactiveEService()]; const result = failedValidation(errors); expect(result).toEqual({ - data: undefined, + hasSucceeded: false, errors: [inactiveEService()], }); }); - it("array of errors or undefined", () => { - const errors = [inactiveEService(), inactiveAgreement(), undefined]; - const result = failedValidation(errors); - expect(result).toEqual({ - data: undefined, - errors: [inactiveEService(), inactiveAgreement()], - }); - }); - it("nested array of errors", () => { - const errors = [[inactiveEService(), inactiveAgreement()], undefined]; - const result = failedValidation(errors); - expect(result).toEqual({ - data: undefined, - errors: [inactiveEService(), inactiveAgreement()], - }); - }); - it("nested array of errors or undefined", () => { - const errors = [ - [inactiveEService(), inactiveAgreement(), undefined], - undefined, - ]; - const result = failedValidation(errors); - expect(result).toEqual({ - data: undefined, - errors: [inactiveEService(), inactiveAgreement()], - }); - }); }); describe("successfulValidation", () => { it("string", () => { const resultString = "result"; const result = successfulValidation(resultString); - expect(result).toEqual({ data: resultString, errors: undefined }); + expect(result).toEqual({ data: resultString, hasSucceeded: true }); }); it("number", () => { const resultNumber = 1; const result = successfulValidation(resultNumber); - expect(result).toEqual({ data: resultNumber, errors: undefined }); + expect(result).toEqual({ data: resultNumber, hasSucceeded: true }); }); }); diff --git a/packages/client-assertion-validation/test/utils.ts b/packages/client-assertion-validation/test/utils.ts index b9eaf0b3b1..e549c9a1c4 100644 --- a/packages/client-assertion-validation/test/utils.ts +++ b/packages/client-assertion-validation/test/utils.ts @@ -8,11 +8,15 @@ import { PurposeId, TenantId, } from "pagopa-interop-models"; +import { expect } from "vitest"; import { ApiKey, ClientAssertionHeader, ClientAssertionValidationRequest, ConsumerKey, + FailedValidation, + SuccessfulValidation, + ValidationResult, } from ".././src/types.js"; import { EXPECTED_CLIENT_ASSERTION_TYPE, @@ -112,3 +116,15 @@ export const getMockAccessTokenRequest = grant_type: EXPECTED_CLIENT_CREDENTIALS_GRANT_TYPE, }; }; + +export function expectValidationFailed( + validationResult: ValidationResult +): asserts validationResult is FailedValidation { + expect(validationResult.hasSucceeded).toBe(false); +} + +export function expectValidationSucceeded( + validationResult: ValidationResult +): asserts validationResult is SuccessfulValidation { + expect(validationResult.hasSucceeded).toBe(true); +} diff --git a/packages/client-assertion-validation/test/validation.test.ts b/packages/client-assertion-validation/test/validation.test.ts index 4925892ad6..566dc6447f 100644 --- a/packages/client-assertion-validation/test/validation.test.ts +++ b/packages/client-assertion-validation/test/validation.test.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import crypto from "crypto"; -import { fail } from "assert"; import { describe, expect, it } from "vitest"; import { ClientId, @@ -46,9 +45,17 @@ import { invalidDigestFormat, purposeIdNotProvided, unexpectedKeyType, + invalidGrantType, + invalidAssertionType, } from "../src/errors.js"; -import { ConsumerKey } from "../src/types.js"; import { + ApiKey, + ClientAssertionValidationRequest, + ConsumerKey, +} from "../src/types.js"; +import { + expectValidationFailed, + expectValidationSucceeded, getMockAccessTokenRequest, getMockApiKey, getMockClientAssertion, @@ -60,38 +67,38 @@ describe("validation test", () => { describe("validateRequestParameters", () => { it("success request parameters", () => { const request = getMockAccessTokenRequest(); - const { errors } = validateRequestParameters(request); - expect(errors).toBeUndefined(); - }); - - // it("invalidAssertionType", () => { - // TODO how to test this if "something-wrong" can't be assigned to the property? - // possible solution: the property is a string (not literal) and the check is done later - // const wrongAssertionType = "something-wrong"; - // const request = { - // ...getMockAccessTokenRequest(), - // client_assertion_type: wrongAssertionType, - // }; - // const { errors } = validateRequestParameters(request); - // expect(errors).toBeDefined(); - // expect(errors).toHaveLength(1); - // expect(errors![0]).toEqual(invalidAssertionType(wrongAssertionType)); - // }); - - // it("invalidGrantType", () => { - // TODO how to test this if "something-wrong" can't be assigned to the property? - // possible solution: the property is a string (not literal) and the check is done later - // const wrongGrantType = "something-wrong"; - // const request = { - // ...getMockAccessTokenRequest(), - // grant_type: wrongGrantType, - // }; - // // TODO: mock already checks grant type - // const errors = validateRequestParameters(request); - // expect(errors).toBeDefined(); - // expect(errors).toHaveLength(1); - // expect(errors![0]).toEqual(invalidGrantType(wrongGrantType)); - // }); + const validation = validateRequestParameters(request); + expect(validation.hasSucceeded).toBeTruthy(); + }); + + it("invalidAssertionType", () => { + const wrongAssertionType = "something-wrong"; + const request: ClientAssertionValidationRequest = { + ...getMockAccessTokenRequest(), + // @ts-expect-error for testing + client_assertion_type: wrongAssertionType, + }; + const validation = validateRequestParameters(request); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual( + invalidAssertionType(wrongAssertionType) + ); + }); + + it("invalidGrantType", () => { + const wrongGrantType = "something-wrong"; + const request: ClientAssertionValidationRequest = { + ...getMockAccessTokenRequest(), + // @ts-expect-error for testing + grant_type: wrongGrantType, + }; + // TODO: mock already checks grant type + const validation = validateRequestParameters(request); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(invalidGrantType(wrongGrantType)); + }); }); describe("verifyClientAssertion", () => { @@ -101,8 +108,8 @@ describe("validation test", () => { payload: {}, customClaims: {}, }); - const { errors } = verifyClientAssertion(a, undefined); - expect(errors).toBeUndefined(); + const validation = verifyClientAssertion(a, undefined); + expect(validation.hasSucceeded).toBeTruthy(); }); it("invalidAudienceFormat", () => { @@ -111,10 +118,12 @@ describe("validation test", () => { payload: { aud: "random" }, customClaims: {}, }); - const { errors } = verifyClientAssertion(a, undefined); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(invalidAudienceFormat()); + const validation = verifyClientAssertion(a, undefined); + + expectValidationFailed(validation); + + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(invalidAudienceFormat()); }); it("invalidAudience", () => { @@ -123,34 +132,34 @@ describe("validation test", () => { payload: { aud: ["random"] }, customClaims: {}, }); - const { errors } = verifyClientAssertion(a, undefined); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(invalidAudience()); + const validation = verifyClientAssertion(a, undefined); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(invalidAudience()); }); it("invalidClientAssertionFormat", () => { - const { errors } = verifyClientAssertion("not a jwt", undefined); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(invalidClientAssertionFormat()); + const validation = verifyClientAssertion("not a jwt", undefined); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(invalidClientAssertionFormat()); }); it("invalidClientAssertionFormat", () => { - const { errors } = verifyClientAssertion("not.a.jwt", undefined); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(invalidClientAssertionFormat()); + const validation = verifyClientAssertion("not.a.jwt", undefined); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(invalidClientAssertionFormat()); }); it("invalidClientAssertionFormat", () => { - const { errors } = verifyClientAssertion( + const validation = verifyClientAssertion( `${generateId()}.${generateId()}`, undefined ); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(invalidClientAssertionFormat()); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(invalidClientAssertionFormat()); }); it("unexpectedClientAssertionPayload", () => { @@ -166,10 +175,10 @@ describe("validation test", () => { }; const jws = jwt.sign("actualPayload", key, options); - const { errors } = verifyClientAssertion(jws, undefined); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(unexpectedClientAssertionPayload()); + const validation = verifyClientAssertion(jws, undefined); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(unexpectedClientAssertionPayload()); }); it("jtiNotFound", () => { @@ -178,10 +187,10 @@ describe("validation test", () => { payload: { jti: undefined }, customClaims: {}, }); - const { errors } = verifyClientAssertion(a, undefined); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(jtiNotFound()); + const validation = verifyClientAssertion(a, undefined); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(jtiNotFound()); }); it.skip("iatNotFound", () => { @@ -192,10 +201,10 @@ describe("validation test", () => { payload: {}, customClaims: { key: 1 }, }); - const { errors } = verifyClientAssertion(a, undefined); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(issuedAtNotFound()); + const validation = verifyClientAssertion(a, undefined); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(issuedAtNotFound()); }); it("expNotFound", () => { @@ -222,10 +231,10 @@ describe("validation test", () => { }, }; const jws = jwt.sign(payload, keySet.privateKey, options); - const { errors } = verifyClientAssertion(jws, undefined); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(expNotFound()); + const validation = verifyClientAssertion(jws, undefined); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(expNotFound()); }); it("issuerNotFound", () => { @@ -234,10 +243,10 @@ describe("validation test", () => { payload: { iss: undefined }, customClaims: {}, }); - const { errors } = verifyClientAssertion(jws, undefined); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(issuerNotFound()); + const validation = verifyClientAssertion(jws, undefined); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(issuerNotFound()); }); it("subjectNotFound", () => { @@ -246,10 +255,10 @@ describe("validation test", () => { payload: { sub: undefined }, customClaims: {}, }); - const { errors } = verifyClientAssertion(jws, undefined); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(subjectNotFound()); + const validation = verifyClientAssertion(jws, undefined); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(subjectNotFound()); }); it("invalidSubject", () => { @@ -259,10 +268,10 @@ describe("validation test", () => { payload: { sub: subject }, customClaims: {}, }); - const { errors } = verifyClientAssertion(jws, generateId()); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(invalidSubject(subject)); + const validation = verifyClientAssertion(jws, generateId()); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(invalidSubject(subject)); }); it("invalidSubjectFormat", () => { @@ -273,10 +282,10 @@ describe("validation test", () => { payload: { sub: subject }, customClaims: {}, }); - const { errors } = verifyClientAssertion(jws, clientId); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(invalidSubjectFormat(subject)); + const validation = verifyClientAssertion(jws, clientId); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(invalidSubjectFormat(subject)); }); it("invalidPurposeIdClaimFormat", () => { @@ -288,10 +297,12 @@ describe("validation test", () => { purposeId: notPurposeId, }, }); - const { errors } = verifyClientAssertion(jws, undefined); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(invalidPurposeIdClaimFormat(notPurposeId)); + const validation = verifyClientAssertion(jws, undefined); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual( + invalidPurposeIdClaimFormat(notPurposeId) + ); }); it("invalidClientIdFormat", () => { @@ -301,10 +312,10 @@ describe("validation test", () => { payload: {}, customClaims: {}, }); - const { errors } = verifyClientAssertion(jws, notClientId); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(invalidClientIdFormat(notClientId)); + const validation = verifyClientAssertion(jws, notClientId); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(invalidClientIdFormat(notClientId)); }); it("digestClaimNotFound", () => { @@ -315,10 +326,10 @@ describe("validation test", () => { digest: undefined, }, }); - const { errors } = verifyClientAssertion(jws, undefined); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(digestClaimNotFound()); + const validation = verifyClientAssertion(jws, undefined); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(digestClaimNotFound()); }); it("invalidDigestFormat", () => { @@ -327,10 +338,10 @@ describe("validation test", () => { payload: {}, customClaims: { digest: { alg: "alg", invalidProp: true } }, }); - const { errors } = verifyClientAssertion(jws, undefined); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(invalidDigestFormat()); + const validation = verifyClientAssertion(jws, undefined); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(invalidDigestFormat()); }); it("invalidHashLength", () => { @@ -341,10 +352,10 @@ describe("validation test", () => { digest: { alg: "SHA256", value: "TODO string of wrong length" }, }, }); - const { errors } = verifyClientAssertion(jws, undefined); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(invalidHashLength("SHA256")); + const validation = verifyClientAssertion(jws, undefined); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(invalidHashLength("SHA256")); }); it("InvalidHashAlgorithm", () => { @@ -355,10 +366,10 @@ describe("validation test", () => { digest: { alg: "wrong alg", value: value64chars }, }, }); - const { errors } = verifyClientAssertion(jws, undefined); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(invalidHashAlgorithm()); + const validation = verifyClientAssertion(jws, undefined); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(invalidHashAlgorithm()); }); it.skip("AlgorithmNotFound", () => { @@ -368,10 +379,10 @@ describe("validation test", () => { payload: {}, customClaims: {}, }); - const { errors } = verifyClientAssertion(jws, undefined); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(algorithmNotFound()); + const validation = verifyClientAssertion(jws, undefined); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(algorithmNotFound()); }); it("AlgorithmNotAllowed", () => { @@ -381,10 +392,10 @@ describe("validation test", () => { payload: {}, customClaims: {}, }); - const { errors } = verifyClientAssertion(jws, undefined); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(algorithmNotAllowed(notAllowedAlg)); + const validation = verifyClientAssertion(jws, undefined); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(algorithmNotAllowed(notAllowedAlg)); }); it("InvalidKidFormat", () => { @@ -393,10 +404,10 @@ describe("validation test", () => { payload: {}, customClaims: {}, }); - const { errors } = verifyClientAssertion(jws, undefined); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(invalidKidFormat()); + const validation = verifyClientAssertion(jws, undefined); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(invalidKidFormat()); }); }); @@ -428,8 +439,8 @@ describe("validation test", () => { ...getMockConsumerKey(), publicKey, }; - const { errors } = verifyClientAssertionSignature(jws, mockConsumerKey); - expect(errors).toBeUndefined(); + const validation = verifyClientAssertionSignature(jws, mockConsumerKey); + expect(validation.hasSucceeded).toBeTruthy(); }); it.skip("invalidClientAssertionSignatureType", () => { @@ -466,20 +477,20 @@ describe("validation test", () => { ...getMockConsumerKey(), publicKey, }; - const { errors } = verifyClientAssertionSignature(jws, mockConsumerKey); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(tokenExpiredError()); + const validation = verifyClientAssertionSignature(jws, mockConsumerKey); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(tokenExpiredError()); }); it("jsonWebTokenError", () => { const mockKey = getMockConsumerKey(); - const { errors } = verifyClientAssertionSignature( + const validation = verifyClientAssertionSignature( "not-a-valid-jws", mockKey ); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0].title).toEqual(jsonWebTokenError("").title); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0].title).toEqual(jsonWebTokenError("").title); }); it("notBeforeError", () => { const threeHoursAgo = new Date(); @@ -513,14 +524,10 @@ describe("validation test", () => { publicKey, }; - const { errors } = verifyClientAssertionSignature(jws, mockConsumerKey); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(notBeforeError()); - }); - it.skip("clientAssertionSignatureVerificationFailure", () => { - // TODO: not sure when this happens - expect(1).toBe(1); + const validation = verifyClientAssertionSignature(jws, mockConsumerKey); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(notBeforeError()); }); }); @@ -533,8 +540,8 @@ describe("validation test", () => { purposeState: itemState.active, }; validatePlatformState(mockKey); - const { errors } = validatePlatformState(mockKey); - expect(errors).toBeUndefined(); + const validation = validatePlatformState(mockKey); + expectValidationSucceeded(validation); }); it("inactiveAgreement", () => { @@ -543,11 +550,11 @@ describe("validation test", () => { agreementState: itemState.inactive, }; validatePlatformState(mockKey); - const { errors } = validatePlatformState(mockKey); + const validation = validatePlatformState(mockKey); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(inactiveAgreement()); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(inactiveAgreement()); }); it("inactiveAgreement", () => { const mockKey: ConsumerKey = { @@ -555,11 +562,11 @@ describe("validation test", () => { descriptorState: itemState.inactive, }; validatePlatformState(mockKey); - const { errors } = validatePlatformState(mockKey); + const validation = validatePlatformState(mockKey); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(inactiveEService()); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(inactiveEService()); }); it("inactivePurpose", () => { const mockKey: ConsumerKey = { @@ -567,11 +574,11 @@ describe("validation test", () => { purposeState: itemState.inactive, }; validatePlatformState(mockKey); - const { errors } = validatePlatformState(mockKey); + const validation = validatePlatformState(mockKey); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(inactivePurpose()); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(inactivePurpose()); }); }); @@ -581,7 +588,7 @@ describe("validation test", () => { ...getMockConsumerKey(), clientKind: clientKindTokenStates.api, }; - const { data: mockClientAssertion } = verifyClientAssertion( + const caValidation = verifyClientAssertion( getMockClientAssertion({ customHeader: {}, payload: {}, @@ -589,48 +596,52 @@ describe("validation test", () => { }), undefined ); - if (!mockClientAssertion) { - fail(); - } - const { errors } = validateClientKindAndPlatformState( + + expectValidationSucceeded(caValidation); + + const validation = validateClientKindAndPlatformState( mockConsumerKey, - mockClientAssertion + caValidation.data + ); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual( + unexpectedKeyType(mockConsumerKey.clientKind) + ); + }); + + it("unexpectedKeyType (apiKey and clientKindTokenStates.consumer)", () => { + // How to test this? The goal is to pass an api key to validateClientKindAndPlatformState (with kind clientKindTokenStates.consumer) + const mockApiKey: ApiKey = { + ...getMockApiKey(), + // @ts-expect-error for testing + clientKind: clientKindTokenStates.consumer, + }; + const caValidation = verifyClientAssertion( + getMockClientAssertion({ + customHeader: {}, + payload: {}, + customClaims: {}, + }), + undefined + ); + + expectValidationSucceeded(caValidation); + + const validation = validateClientKindAndPlatformState( + mockApiKey, + caValidation.data ); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(unexpectedKeyType(mockConsumerKey.clientKind)); - }); - - // it("unexpectedKeyType (apiKey and clientKindTokenStates.consumer)", () => { - // // How to test this? The goal is to pass an api key to validateClientKindAndPlatformState (with kind clientKindTokenStates.consumer) - // const mockApiKey = { - // ...getMockApiKey(), - // clientKind: clientKindTokenStates.consumer, - // }; - // const { data: mockClientAssertion } = verifyClientAssertion( - // getMockClientAssertion({ - // customHeader: {}, - // payload: {}, - // customClaims: {}, - // }), - // undefined - // ); - // if (!mockClientAssertion) { - // fail(); - // } - // const { errors } = validateClientKindAndPlatformState( - // // FIX - // mockApiKey, - // mockClientAssertion - // ); - // expect(errors).toBeDefined(); - // expect(errors).toHaveLength(1); - // expect(errors![0]).toEqual(unexpectedKeyType(mockApiKey.clientKind)); - // }); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual( + unexpectedKeyType(mockApiKey.clientKind) + ); + }); it("success (consumerKey and clientKindTokenStates.consumer; valid platform states)", () => { const mockConsumerKey = getMockConsumerKey(); - const { data: mockClientAssertion } = verifyClientAssertion( + const caValidation = verifyClientAssertion( getMockClientAssertion({ customHeader: {}, payload: { purposeId: generateId() }, @@ -638,14 +649,14 @@ describe("validation test", () => { }), undefined ); - if (!mockClientAssertion) { - fail(); - } - const { errors } = validateClientKindAndPlatformState( + + expectValidationSucceeded(caValidation); + + const validation = validateClientKindAndPlatformState( mockConsumerKey, - mockClientAssertion + caValidation.data ); - expect(errors).toBeUndefined(); + expectValidationSucceeded(validation); }); it("inactiveEService (consumerKey and clientKindTokenStates.consumer; invalid platform states)", () => { @@ -653,7 +664,7 @@ describe("validation test", () => { ...getMockConsumerKey(), descriptorState: itemState.inactive, }; - const { data: mockClientAssertion } = verifyClientAssertion( + const caValidation = verifyClientAssertion( getMockClientAssertion({ customHeader: {}, payload: { purposeId: generateId() }, @@ -661,21 +672,21 @@ describe("validation test", () => { }), undefined ); - if (!mockClientAssertion) { - fail(); - } - const { errors } = validateClientKindAndPlatformState( + + expectValidationSucceeded(caValidation); + + const validation = validateClientKindAndPlatformState( mockConsumerKey, - mockClientAssertion + caValidation.data ); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(inactiveEService()); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(inactiveEService()); }); it("success (apiKey and clientKindTokenStates.api)", () => { const mockApiKey = getMockApiKey(); - const { data: mockClientAssertion } = verifyClientAssertion( + const caValidation = verifyClientAssertion( getMockClientAssertion({ customHeader: {}, payload: {}, @@ -683,19 +694,21 @@ describe("validation test", () => { }), undefined ); - if (!mockClientAssertion) { - fail(); - } - const { errors } = validateClientKindAndPlatformState( + + expectValidationSucceeded(caValidation); + + const mockClientAssertion = caValidation.data; + + const validation = validateClientKindAndPlatformState( mockApiKey, mockClientAssertion ); - expect(errors).toBeUndefined(); + expectValidationSucceeded(validation); }); it("purposeIdNotProvided", () => { const mockConsumerKey = getMockConsumerKey(); - const { data: mockClientAssertion } = verifyClientAssertion( + const caValidation = verifyClientAssertion( getMockClientAssertion({ customHeader: {}, payload: { purposeId: undefined }, @@ -703,16 +716,16 @@ describe("validation test", () => { }), undefined ); - if (!mockClientAssertion) { - fail(); - } - const { errors } = validateClientKindAndPlatformState( + + expectValidationSucceeded(caValidation); + + const validation = validateClientKindAndPlatformState( mockConsumerKey, - mockClientAssertion + caValidation.data ); - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors![0]).toEqual(purposeIdNotProvided()); + expectValidationFailed(validation); + expect(validation.errors).toHaveLength(1); + expect(validation.errors[0]).toEqual(purposeIdNotProvided()); }); }); }); From 9aa6e322d776f175c04c98ae2f56c50c47783864 Mon Sep 17 00:00:00 2001 From: Carmine Porricelli Date: Thu, 26 Sep 2024 12:05:26 +0200 Subject: [PATCH 240/241] Update validation.ts --- .../src/validation.ts | 79 ++++++++----------- 1 file changed, 34 insertions(+), 45 deletions(-) diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index ecd29614a7..394f17a4fb 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -85,58 +85,47 @@ export const verifyClientAssertion = ( return failedValidation([unexpectedClientAssertionPayload()]); } - const kidValidation = validateKid(decoded.header.kid); - const algValidation = validateAlgorithm(decoded.header.alg); - const subValidation = validateSub(decoded.payload.sub, clientId); - const purposeIdValidation = validatePurposeId(decoded.payload.purposeId); - const jtiValidation = validateJti(decoded.payload.jti); - const iatValidation = validateIat(decoded.payload.iat); - const issValidation = validateIss(decoded.payload.iss); - const audValidation = validateAudience(decoded.payload.aud); - const expValidation = validateExp(decoded.payload.exp); - const digestValidation = validateDigest(decoded.payload.digest); - - const errors = [ - kidValidation, - algValidation, - subValidation, - purposeIdValidation, - jtiValidation, - iatValidation, - issValidation, - audValidation, - expValidation, - digestValidation, - ] + const validations = [ + validateKid(decoded.header.kid), + validateAlgorithm(decoded.header.alg), + validateSub(decoded.payload.sub, clientId), + validatePurposeId(decoded.payload.purposeId), + validateJti(decoded.payload.jti), + validateIat(decoded.payload.iat), + validateIss(decoded.payload.iss), + validateAudience(decoded.payload.aud), + validateExp(decoded.payload.exp), + validateDigest(decoded.payload.digest), + ] as const; + + const errors = validations .filter((validation) => !validation.hasSucceeded) .flatMap(({ errors }) => errors); - if ( - kidValidation.hasSucceeded && - algValidation.hasSucceeded && - subValidation.hasSucceeded && - purposeIdValidation.hasSucceeded && - jtiValidation.hasSucceeded && - iatValidation.hasSucceeded && - issValidation.hasSucceeded && - audValidation.hasSucceeded && - expValidation.hasSucceeded && - digestValidation.hasSucceeded - ) { + if (errors.length === 0) { + // ts-server does not understand that the errors being of lenght zero + // means that we have all successfull validations in the tuple. + // We force it with a type assertion that tells it exactly that. + const successfullValidations = validations as { + [K in keyof typeof validations]: ((typeof validations)[K] & { + hasSucceeded: true; + })["data"]; + }; + return successfulValidation({ header: { - kid: kidValidation.data, - alg: algValidation.data, + kid: successfullValidations[0].data, + alg: successfullValidations[1].data, }, payload: { - sub: subValidation.data, - purposeId: purposeIdValidation.data, - jti: jtiValidation.data, - iat: iatValidation.data, - iss: issValidation.data, - aud: audValidation.data, - exp: expValidation.data, - digest: digestValidation.data, + sub: successfullValidations[2].data, + purposeId: successfullValidations[3].data, + jti: successfullValidations[4].data, + iat: successfullValidations[5].data, + iss: successfullValidations[6].data, + aud: successfullValidations[7].data, + exp: successfullValidations[8].data, + digest: successfullValidations[9].data, }, } satisfies ValidatedClientAssertion); } From b0ba1a84ee2138a17e62435def9f0bcfb36ee516 Mon Sep 17 00:00:00 2001 From: Carmine Porricelli Date: Thu, 26 Sep 2024 14:16:41 +0200 Subject: [PATCH 241/241] Fix type error --- packages/client-assertion-validation/src/validation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client-assertion-validation/src/validation.ts b/packages/client-assertion-validation/src/validation.ts index 394f17a4fb..edd2f7a1c6 100644 --- a/packages/client-assertion-validation/src/validation.ts +++ b/packages/client-assertion-validation/src/validation.ts @@ -107,9 +107,9 @@ export const verifyClientAssertion = ( // means that we have all successfull validations in the tuple. // We force it with a type assertion that tells it exactly that. const successfullValidations = validations as { - [K in keyof typeof validations]: ((typeof validations)[K] & { + [K in keyof typeof validations]: (typeof validations)[K] & { hasSucceeded: true; - })["data"]; + }; }; return successfulValidation({