diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index 6ad2e6b799..ff27b605a9 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -1,12 +1,183 @@ import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; -import { EServiceEventEnvelopeV2 } from "pagopa-interop-models"; +import { + Descriptor, + DescriptorId, + descriptorState, + EService, + EServiceEventEnvelopeV2, + EServiceV2, + fromEServiceV2, + makeGSIPKEServiceIdDescriptorId, + makePlatformStatesEServiceDescriptorPK, + missingKafkaMessageDataError, + PlatformStatesCatalogEntry, + unsafeBrandId, +} from "pagopa-interop-models"; import { match } from "ts-pattern"; +import { + deleteCatalogEntry, + descriptorStateToItemState, + readCatalogEntry, + updateDescriptorStateInPlatformStatesEntry, + updateDescriptorStateInTokenGenerationStatesTable, + writeCatalogEntry, +} from "./utils.js"; export async function handleMessageV2( message: EServiceEventEnvelopeV2, - _dynamoDBClient: DynamoDBClient + dynamoDBClient: DynamoDBClient ): Promise { await match(message) + .with({ type: "EServiceDescriptorPublished" }, async (msg) => { + const { eservice, descriptor } = parseEServiceAndDescriptor( + msg.data.eservice, + unsafeBrandId(msg.data.descriptorId), + message.type + ); + const previousDescriptor = eservice.descriptors.find( + (d) => d.version === (Number(descriptor.version) - 1).toString() + ); + + // flow for current descriptor + const processCurrentDescriptor = async (): Promise => { + const primaryKeyCurrent = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: descriptor.id, + }); + const existingCatalogEntryCurrent = await readCatalogEntry( + primaryKeyCurrent, + dynamoDBClient + ); + if (existingCatalogEntryCurrent) { + if (existingCatalogEntryCurrent.version > msg.version) { + // Stops processing if the message is older than the catalog entry + return Promise.resolve(); + } else { + await updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, + primaryKeyCurrent, + descriptorStateToItemState(descriptor.state), + msg.version + ); + } + } else { + const catalogEntry: PlatformStatesCatalogEntry = { + PK: primaryKeyCurrent, + state: descriptorStateToItemState(descriptor.state), + descriptorAudience: descriptor.audience, + descriptorVoucherLifespan: descriptor.voucherLifespan, + 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, + descriptorStateToItemState(descriptor.state), + dynamoDBClient + ); + }; + + await processCurrentDescriptor(); + + // flow for previous descriptor + + if ( + !previousDescriptor || + previousDescriptor.state !== descriptorState.archived + ) { + return Promise.resolve(); + } else { + const primaryKeyPrevious = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: previousDescriptor.id, + }); + + await deleteCatalogEntry(primaryKeyPrevious, dynamoDBClient); + + // token-generation-states + const eserviceId_descriptorId_previous = + makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: previousDescriptor.id, + }); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId_previous, + descriptorStateToItemState(previousDescriptor.state), + dynamoDBClient + ); + } + }) + .with( + { type: "EServiceDescriptorActivated" }, + { type: "EServiceDescriptorSuspended" }, + async (msg) => { + const { eservice, descriptor } = parseEServiceAndDescriptor( + msg.data.eservice, + unsafeBrandId(msg.data.descriptorId), + message.type + ); + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: descriptor.id, + }); + const catalogEntry = await readCatalogEntry(primaryKey, dynamoDBClient); + + if (!catalogEntry || catalogEntry.version > msg.version) { + return Promise.resolve(); + } else { + await updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, + primaryKey, + descriptorStateToItemState(descriptor.state), + msg.version + ); + + // token-generation-states + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: descriptor.id, + }); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptorStateToItemState(descriptor.state), + dynamoDBClient + ); + } + } + ) + .with({ type: "EServiceDescriptorArchived" }, async (msg) => { + const { eservice, descriptor } = parseEServiceAndDescriptor( + msg.data.eservice, + unsafeBrandId(msg.data.descriptorId), + msg.type + ); + + const primaryKey = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: unsafeBrandId(msg.data.descriptorId), + }); + await deleteCatalogEntry(primaryKey, dynamoDBClient); + + // token-generation-states + const descriptorId = unsafeBrandId(msg.data.descriptorId); + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId, + }); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptorStateToItemState(descriptor.state), + dynamoDBClient + ); + }) .with( { type: "EServiceDeleted" }, { type: "EServiceAdded" }, @@ -16,10 +187,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" }, @@ -34,3 +201,21 @@ export async function handleMessageV2( ) .exhaustive(); } + +export const parseEServiceAndDescriptor = ( + eserviceV2: EServiceV2 | undefined, + descriptorId: DescriptorId, + eventType: string +): { eservice: EService; descriptor: Descriptor } => { + if (!eserviceV2) { + throw missingKafkaMessageDataError("eservice", eventType); + } + + const eservice = fromEServiceV2(eserviceV2); + + const descriptor = eservice.descriptors.find((d) => d.id === descriptorId); + if (!descriptor) { + 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 c6c03ed13b..e01e9d572c 100644 --- a/packages/catalog-platformstate-writer/src/utils.ts +++ b/packages/catalog-platformstate-writer/src/utils.ts @@ -1,9 +1,16 @@ import { + descriptorState, + DescriptorState, genericInternalError, + GSIPKEServiceIdDescriptorId, + itemState, + ItemState, PlatformStatesCatalogEntry, PlatformStatesEServiceDescriptorPK, + TokenGenerationStatesClientPurposeEntry, } from "pagopa-interop-models"; import { + AttributeValue, DeleteItemCommand, DeleteItemInput, DynamoDBClient, @@ -12,8 +19,14 @@ import { GetItemInput, PutItemCommand, PutItemInput, + QueryCommand, + QueryCommandOutput, + QueryInput, + UpdateItemCommand, + 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 ( @@ -21,6 +34,7 @@ export const writeCatalogEntry = async ( dynamoDBClient: DynamoDBClient ): Promise => { const input: PutItemInput = { + ConditionExpression: "attribute_not_exists(PK)", Item: { PK: { S: catalogEntry.PK, @@ -29,7 +43,9 @@ export const writeCatalogEntry = async ( S: catalogEntry.state, }, descriptorAudience: { - S: catalogEntry.descriptorAudience, + L: catalogEntry.descriptorAudience.map((item) => ({ + S: item, + })), }, descriptorVoucherLifespan: { N: catalogEntry.descriptorVoucherLifespan.toString(), @@ -90,3 +106,144 @@ export const deleteCatalogEntry = async ( const command = new DeleteItemCommand(input); await dynamoDBClient.send(command); }; + +export const descriptorStateToItemState = (state: DescriptorState): ItemState => + state === descriptorState.published || state === descriptorState.deprecated + ? itemState.active + : itemState.inactive; + +export const updateDescriptorStateInPlatformStatesEntry = async ( + dynamoDBClient: DynamoDBClient, + primaryKey: PlatformStatesEServiceDescriptorPK, + state: ItemState, + version: number +): Promise => { + const input: UpdateItemInput = { + ConditionExpression: "attribute_exists(PK)", + 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: "NONE", + }; + const command = new UpdateItemCommand(input); + await dynamoDBClient.send(command); +}; + +export const updateDescriptorStateInTokenGenerationStatesTable = async ( + eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, + descriptorState: ItemState, + dynamoDBClient: DynamoDBClient +): Promise => { + const runPaginatedQuery = async ( + eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, + dynamoDBClient: DynamoDBClient, + exclusiveStartKey?: Record + ): Promise => { + const input: QueryInput = { + TableName: config.tokenGenerationReadModelTableNameTokenGeneration, + IndexName: "Descriptor", + 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 (!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)} ` + ); + } + + await updateDescriptorStateEntriesInTokenGenerationStatesTable( + descriptorState, + dynamoDBClient, + tokenStateEntries.data + ); + + if (!data.LastEvaluatedKey) { + return tokenStateEntries.data; + } else { + return [ + ...tokenStateEntries.data, + ...(await runPaginatedQuery( + eserviceId_descriptorId, + dynamoDBClient, + data.LastEvaluatedKey + )), + ]; + } + } + }; + + return await runPaginatedQuery( + eserviceId_descriptorId, + dynamoDBClient, + undefined + ); +}; + +const updateDescriptorStateEntriesInTokenGenerationStatesTable = async ( + descriptorState: ItemState, + dynamoDBClient: DynamoDBClient, + entriesToUpdate: TokenGenerationStatesClientPurposeEntry[] +): Promise => { + for (const entry of entriesToUpdate) { + const input: UpdateItemInput = { + ConditionExpression: "attribute_exists(GSIPK_eserviceId_descriptorId)", + Key: { + PK: { + S: entry.PK, + }, + }, + ExpressionAttributeValues: { + ":newState": { + S: descriptorState, + }, + ":newUpdateAt": { + S: new Date().toISOString(), + }, + }, + UpdateExpression: + "SET descriptorState = :newState, updatedAt = :newUpdateAt", + TableName: config.tokenGenerationReadModelTableNameTokenGeneration, + ReturnValues: "NONE", + }; + const command = new UpdateItemCommand(input); + await dynamoDBClient.send(command); + } +}; diff --git a/packages/catalog-platformstate-writer/test/consumerServiceV2.test.ts b/packages/catalog-platformstate-writer/test/consumerServiceV2.test.ts new file mode 100644 index 0000000000..104c5cf158 --- /dev/null +++ b/packages/catalog-platformstate-writer/test/consumerServiceV2.test.ts @@ -0,0 +1,1201 @@ +/* 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, + EServiceDescriptorActivatedV2, + EServiceDescriptorArchivedV2, + EServiceDescriptorPublishedV2, + EServiceDescriptorSuspendedV2, + EServiceEventEnvelope, + PlatformStatesCatalogEntry, + TokenGenerationStatesClientPurposeEntry, + descriptorState, + generateId, + itemState, + makeGSIPKEServiceIdDescriptorId, + makePlatformStatesEServiceDescriptorPK, + makeTokenGenerationStatesClientKidPurposePK, + toEServiceV2, +} from "pagopa-interop-models"; +import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; +import { + getMockDescriptor, + getMockEService, + getMockDocument, + getMockTokenStatesClientPurposeEntry, + buildDynamoDBTables, + deleteDynamoDBTables, + readTokenStateEntriesByEserviceIdAndDescriptorId, + writeTokenStateEntry, +} from "pagopa-interop-commons-test"; +import { readCatalogEntry, writeCatalogEntry } from "../src/utils.js"; +import { handleMessageV2 } from "../src/consumerServiceV2.js"; +import { config, sleep } from "./utils.js"; + +describe("integration tests V2 events", async () => { + if (!config) { + fail(); + } + const dynamoDBClient = new DynamoDBClient({ + endpoint: `http://localhost:${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("EServiceDescriptorActivated", () => { + it("should do no operation if the existing table entry is more recent", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it/test1", "pagopa.it/test2"], + 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, + 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, + 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, + 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([ + previousTokenStateEntry2, + previousTokenStateEntry1, + ]) + ); + }); + it("should update the entry if the incoming version is more recent than existing table entry", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it/test1", "pagopa.it/test2"], + 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: 3, + 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, + 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, + 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, + 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: 3, + 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, + ]) + ); + }); + it("should do no operation if the table entry doesn't exist", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it/test1", "pagopa.it/test2"], + 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, + 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, + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + + await sleep(1000, mockDate); + expect( + handleMessageV2(message, dynamoDBClient) + ).resolves.not.toThrowError(); + + // platform-states + const retrievedCatalogEntry = await readCatalogEntry( + catalogEntryPrimaryKey, + 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, + ]) + ); + }); + }); + + describe("EServiceDescriptorArchived", () => { + it("should delete the entry from platform states and update token generation states", async () => { + const archivedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it/test1", "pagopa.it/test2"], + 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, + 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, + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.active, + descriptorAudience: archivedDescriptor.audience, + 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", () => { + describe("the eservice has 1 descriptor", () => { + it("should add the entry if it doesn't exist", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it/test1", "pagopa.it/test2"], + 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, + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience, + 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 expectedEntry: PlatformStatesCatalogEntry = { + PK: primaryKey, + state: itemState.active, + descriptorAudience: publishedDescriptor.audience, + descriptorVoucherLifespan: publishedDescriptor.voucherLifespan, + 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.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 do no operation if the existing table entry is more recent", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it/test1", "pagopa.it/test2"], + 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, + 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, + }); + + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience, + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience, + 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("should update the entry if incoming version is more recent than existing table entry", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it/test1", "pagopa.it/test2"], + 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, + 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, + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.inactive, + descriptorAudience: publishedDescriptor.audience, + 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("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 in platform states and update the entries in token generation states", async () => { + const archivedDescriptor: Descriptor = { + ...getMockDescriptor(), + state: descriptorState.archived, + audience: ["pagopa.it/test1", "pagopa.it/test2"], + interface: getMockDocument(), + version: "1", + publishedAt: new Date(), + archivedAt: new Date(), + }; + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(), + archivedAt: new Date(), + state: descriptorState.published, + audience: ["pagopa.it/test1", "pagopa.it/test2"], + 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(), + }; + + 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, + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry1, dynamoDBClient); + + const previousTokenStateEntry2: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK2), + descriptorState: itemState.active, + descriptorAudience: publishedDescriptor.audience, + 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, + ]) + ); + }); + }); + }); + + describe("EServiceDescriptorSuspended", () => { + 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/test1", "pagopa.it/test2"], + 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, + descriptorAudience: suspendedDescriptor.audience, + descriptorVoucherLifespan: suspendedDescriptor.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: suspendedDescriptor.id, + }); + const previousTokenStateEntry1: TokenGenerationStatesClientPurposeEntry = + { + ...getMockTokenStatesClientPurposeEntry(tokenStateEntryPK1), + descriptorState: itemState.active, + descriptorAudience: suspendedDescriptor.audience, + 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, + 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("should update the entry: incoming has version 3; previous entry has version 2", async () => { + const suspendedDescriptor: Descriptor = { + ...getMockDescriptor(), + audience: ["pagopa.it/test1", "pagopa.it/test2"], + 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, + descriptorAudience: suspendedDescriptor.audience, + 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, + 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, + 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/test1", "pagopa.it/test2"], + 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 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, + 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, + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + + await sleep(1000, mockDate); + expect( + handleMessageV2(message, dynamoDBClient) + ).resolves.not.toThrowError(); + + // platform-states + const retrievedCatalogEntry = await readCatalogEntry( + catalogEntryPrimaryKey, + 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, + ]) + ); + }); + }); +}); 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 new file mode 100644 index 0000000000..58c834f1c4 --- /dev/null +++ b/packages/catalog-platformstate-writer/test/utils.test.ts @@ -0,0 +1,489 @@ +/* eslint-disable @typescript-eslint/no-floating-promises */ +import { fail } from "assert"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, + vi, +} from "vitest"; +import { + PlatformStatesCatalogEntry, + TokenGenerationStatesClientPurposeEntry, + descriptorState, + generateId, + itemState, + makeGSIPKEServiceIdDescriptorId, + makePlatformStatesEServiceDescriptorPK, + makeTokenGenerationStatesClientKidPurposePK, +} from "pagopa-interop-models"; +import { + ConditionalCheckFailedException, + DynamoDBClient, +} from "@aws-sdk/client-dynamodb"; +import { + getMockTokenStatesClientPurposeEntry, + buildDynamoDBTables, + deleteDynamoDBTables, + readTokenStateEntriesByEserviceIdAndDescriptorId, + readAllTokenStateItems, + writeTokenStateEntry, +} from "pagopa-interop-commons-test"; +import { + deleteCatalogEntry, + descriptorStateToItemState, + readCatalogEntry, + updateDescriptorStateInPlatformStatesEntry, + updateDescriptorStateInTokenGenerationStatesTable, + writeCatalogEntry, +} from "../src/utils.js"; +import { config } from "./utils.js"; + +describe("utils tests", async () => { + if (!config) { + fail(); + } + const dynamoDBClient = new DynamoDBClient({ + credentials: { accessKeyId: "key", secretAccessKey: "secret" }, + region: "eu-central-1", + endpoint: `http://localhost:${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("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/test1", "pagopa.it/test2"], + 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/test1", "pagopa.it/test2"], + 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/test1", "pagopa.it/test2"], + 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/test1", "pagopa.it/test2"], + 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/test1", "pagopa.it/test2"], + 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/test1", "pagopa.it/test2"], + 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/test1", "pagopa.it/test2"], + 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/test1", "pagopa.it/test2"], + 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/test1", "pagopa.it/test2"], + 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/test1", "pagopa.it/test2"], + 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, + itemState.inactive, + 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/test1", "pagopa.it/test2"], + 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/test1", "pagopa.it/test2"], + GSIPK_eserviceId_descriptorId: eserviceId_descriptorId, + }; + await writeTokenStateEntry(previousTokenStateEntry2, dynamoDBClient); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + itemState.active, + 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, + ]) + ); + }); + }); +}); diff --git a/packages/catalog-platformstate-writer/test/utils.ts b/packages/catalog-platformstate-writer/test/utils.ts index aca07c9cc6..3c4071f28a 100644 --- a/packages/catalog-platformstate-writer/test/utils.ts +++ b/packages/catalog-platformstate-writer/test/utils.ts @@ -1,3 +1,13 @@ -import { inject } from "vitest"; +/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { inject, vi } from "vitest"; export const config = inject("tokenGenerationReadModelConfig"); + +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/commons-test/package.json b/packages/commons-test/package.json index 2627f032f2..dde6a915e0 100644 --- a/packages/commons-test/package.json +++ b/packages/commons-test/package.json @@ -19,7 +19,9 @@ "license": "Apache-2.0", "devDependencies": { "@anatine/zod-mock": "3.13.4", + "@aws-sdk/client-dynamodb": "3.637.0", "@aws-sdk/client-sesv2": "3.620.1", + "@aws-sdk/util-dynamodb": "3.658.1", "@pagopa/eslint-config": "3.0.0", "@protobuf-ts/runtime": "2.9.4", "@testcontainers/postgresql": "10.9.0", diff --git a/packages/commons-test/src/index.ts b/packages/commons-test/src/index.ts index 449587b1f8..332cfc56c9 100644 --- a/packages/commons-test/src/index.ts +++ b/packages/commons-test/src/index.ts @@ -6,3 +6,5 @@ export * from "./riskAnalysisTestUtils.js"; export * from "./setupTestContainersVitest.js"; export * from "./setupTestContainersVitestGlobal.js"; export * from "./protobufConvertersToV1/catalogProtobufConverterToV1.js"; +export * from "./setupDynamoDBtables.js"; +export * from "./tokenGenerationReadmodelUtils.js"; diff --git a/packages/commons-test/src/setupDynamoDBtables.ts b/packages/commons-test/src/setupDynamoDBtables.ts new file mode 100644 index 0000000000..e1dcd0a793 --- /dev/null +++ b/packages/commons-test/src/setupDynamoDBtables.ts @@ -0,0 +1,141 @@ +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" }, + { AttributeName: "GSIPK_consumerId_eserviceId", AttributeType: "S" }, + { AttributeName: "GSISK_agreementTimestamp", AttributeType: "S" }, + ], + KeySchema: [{ AttributeName: "PK", KeyType: "HASH" }], + BillingMode: "PAY_PER_REQUEST", + GlobalSecondaryIndexes: [ + { + IndexName: "Agreement", + KeySchema: [ + { + AttributeName: "GSIPK_consumerId_eserviceId", + KeyType: "HASH", + }, + { + AttributeName: "GSISK_agreementTimestamp", + KeyType: "RANGE", + }, + ], + Projection: { + NonKeyAttributes: [], + ProjectionType: "ALL", + }, + }, + ], + }; + 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" }, + { 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", + GlobalSecondaryIndexes: [ + { + IndexName: "Descriptor", + KeySchema: [ + { + AttributeName: "GSIPK_eserviceId_descriptorId", + KeyType: "HASH", + }, + ], + Projection: { + NonKeyAttributes: [], + ProjectionType: "ALL", + }, + }, + { + IndexName: "Agreement", + KeySchema: [ + { + AttributeName: "GSIPK_consumerId_eserviceId", + KeyType: "HASH", + }, + ], + Projection: { + NonKeyAttributes: [], + ProjectionType: "ALL", + }, + }, + { + IndexName: "Purpose", + KeySchema: [{ AttributeName: "GSIPK_purposeId", KeyType: "HASH" }], + Projection: { + NonKeyAttributes: [], + ProjectionType: "ALL", + }, + }, + { + IndexName: "Client", + KeySchema: [{ AttributeName: "GSIPK_clientId", KeyType: "HASH" }], + Projection: { + NonKeyAttributes: [], + ProjectionType: "ALL", + }, + }, + { + IndexName: "Kid", + KeySchema: [{ AttributeName: "GSIPK_kid", KeyType: "HASH" }], + Projection: { + NonKeyAttributes: [], + ProjectionType: "ALL", + }, + }, + { + IndexName: "ClientPurpose", + KeySchema: [ + { AttributeName: "GSIPK_clientId_purposeId", KeyType: "HASH" }, + ], + Projection: { + NonKeyAttributes: [], + ProjectionType: "ALL", + }, + }, + ], + }; + const command2 = new CreateTableCommand(tokenGenerationTableDefinition); + await dynamoDBClient.send(command2); +}; + +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/packages/commons-test/src/setupTestContainersVitestGlobal.ts b/packages/commons-test/src/setupTestContainersVitestGlobal.ts index cb3d22e9ea..ca34f76461 100644 --- a/packages/commons-test/src/setupTestContainersVitestGlobal.ts +++ b/packages/commons-test/src/setupTestContainersVitestGlobal.ts @@ -16,6 +16,7 @@ import { S3Config, TokenGenerationReadModelDbConfig, } from "pagopa-interop-commons"; +import { z } from "zod"; import { TEST_MINIO_PORT, TEST_MONGO_DB_PORT, @@ -33,10 +34,18 @@ import { } from "./containerTestUtils.js"; import { PecEmailManagerConfigTest } from "./testConfig.js"; +const EnhancedTokenGenerationReadModelDbConfig = + TokenGenerationReadModelDbConfig.and( + z.object({ tokenGenerationReadModelDbPort: z.number() }) + ); +type EnhancedTokenGenerationReadModelDbConfig = z.infer< + typeof EnhancedTokenGenerationReadModelDbConfig +>; + declare module "vitest" { export interface ProvidedContext { readModelConfig?: ReadModelDbConfig; - tokenGenerationReadModelConfig?: TokenGenerationReadModelDbConfig; + tokenGenerationReadModelConfig?: EnhancedTokenGenerationReadModelDbConfig; eventStoreConfig?: EventStoreConfig; fileManagerConfig?: FileManagerConfig & LoggerConfig & S3Config; redisRateLimiterConfig?: RedisRateLimiterConfig; @@ -140,15 +149,12 @@ export function setupTestContainersVitestGlobal() { // 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); - tokenGenerationReadModelConfig.data.tokenGenerationReadModelDbHost = - startedDynamoDbContainer.getHost(); - - provide( - "tokenGenerationReadModelConfig", - tokenGenerationReadModelConfig.data - ); + + provide("tokenGenerationReadModelConfig", { + ...tokenGenerationReadModelConfig.data, + tokenGenerationReadModelDbPort: + startedDynamoDbContainer.getMappedPort(TEST_DYNAMODB_PORT), + }); } if (redisRateLimiterConfig.success) { diff --git a/packages/commons-test/src/testUtils.ts b/packages/commons-test/src/testUtils.ts index 00991c7860..e649b1d6c7 100644 --- a/packages/commons-test/src/testUtils.ts +++ b/packages/commons-test/src/testUtils.ts @@ -34,6 +34,17 @@ import { Key, technology, AttributeKind, + itemState, + ClientId, + PurposeId, + TokenGenerationStatesClientPurposeEntry, + makeGSIPKConsumerIdEServiceId, + makeGSIPKClientIdPurposeId, + makeGSIPKEServiceIdDescriptorId, + TokenGenerationStatesClientKidPurposePK, + makeTokenGenerationStatesClientKidPurposePK, + AgreementId, + PurposeVersionId, ProducerKeychain, } from "pagopa-interop-models"; import { AuthData } from "pagopa-interop-commons"; @@ -302,3 +313,50 @@ export const getMockAuthData = (organizationId?: TenantId): AuthData => ({ }, selfcareId: generateId(), }); + +export const getMockTokenStatesClientPurposeEntry = ( + tokenStateEntryPK?: TokenGenerationStatesClientKidPurposePK +): TokenGenerationStatesClientPurposeEntry => { + const clientId = generateId(); + const purposeId = generateId(); + const consumerId = generateId(); + const eserviceId = generateId(); + const descriptorId = generateId(); + const agreementId = generateId(); + const purposeVersionId = generateId(); + + return { + PK: + tokenStateEntryPK || + makeTokenGenerationStatesClientKidPurposePK({ + clientId, + kid: `kid ${Math.random()}`, + purposeId, + }), + descriptorState: itemState.inactive, + descriptorAudience: ["pagopa.it/test1", "pagopa.it/test2"], + updatedAt: new Date().toISOString(), + consumerId, + agreementId, + purposeVersionId, + GSIPK_consumerId_eserviceId: makeGSIPKConsumerIdEServiceId({ + consumerId, + eserviceId, + }), + clientKind: clientKind.consumer, + publicKey: "PEM", + GSIPK_clientId: clientId, + GSIPK_kid: "KID", + agreementState: "ACTIVE", + GSIPK_eserviceId_descriptorId: makeGSIPKEServiceIdDescriptorId({ + eserviceId, + descriptorId, + }), + GSIPK_purposeId: purposeId, + purposeState: itemState.inactive, + GSIPK_clientId_purposeId: makeGSIPKClientIdPurposeId({ + clientId, + purposeId, + }), + }; +}; diff --git a/packages/commons-test/src/tokenGenerationReadmodelUtils.ts b/packages/commons-test/src/tokenGenerationReadmodelUtils.ts new file mode 100644 index 0000000000..4a27dcd8c1 --- /dev/null +++ b/packages/commons-test/src/tokenGenerationReadmodelUtils.ts @@ -0,0 +1,183 @@ +/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { + AttributeValue, + DynamoDBClient, + PutItemCommand, + PutItemInput, + QueryCommand, + QueryCommandOutput, + QueryInput, + ScanCommand, + ScanCommandOutput, + ScanInput, +} from "@aws-sdk/client-dynamodb"; +import { + genericInternalError, + GSIPKEServiceIdDescriptorId, + TokenGenerationStatesClientPurposeEntry, +} from "pagopa-interop-models"; +import { unmarshall } from "@aws-sdk/util-dynamodb"; +import { z } from "zod"; + +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: { + L: tokenStateEntry.descriptorAudience + ? tokenStateEntry.descriptorAudience.map((item) => ({ + S: item, + })) + : [], + }, + 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: "token-generation-states", + }; + const command = new PutItemCommand(input); + await dynamoDBClient.send(command); +}; + +export const readAllTokenStateItems = async ( + dynamoDBClient: DynamoDBClient +): Promise => { + const readInput: ScanInput = { + TableName: "token-generation-states", + }; + 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; + } +}; + +export const readTokenStateEntriesByEserviceIdAndDescriptorId = async ( + eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, + dynamoDBClient: DynamoDBClient +): Promise => { + const runPaginatedQuery = async ( + eserviceId_descriptorId: GSIPKEServiceIdDescriptorId, + dynamoDBClient: DynamoDBClient, + exclusiveStartKey?: Record + ): Promise => { + const input: QueryInput = { + TableName: "token-generation-states", + IndexName: "Descriptor", + 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 (!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)} ` + ); + } + + if (!data.LastEvaluatedKey) { + return tokenStateEntries.data; + } else { + return [ + ...tokenStateEntries.data, + ...(await runPaginatedQuery( + eserviceId_descriptorId, + dynamoDBClient, + data.LastEvaluatedKey + )), + ]; + } + } + }; + + return await runPaginatedQuery( + eserviceId_descriptorId, + dynamoDBClient, + undefined + ); +}; diff --git a/packages/commons/src/config/tokenGenerationReadmodelDbConfig.ts b/packages/commons/src/config/tokenGenerationReadmodelDbConfig.ts index 0b75478e70..e9ee8c01f7 100644 --- a/packages/commons/src/config/tokenGenerationReadmodelDbConfig.ts +++ b/packages/commons/src/config/tokenGenerationReadmodelDbConfig.ts @@ -2,14 +2,10 @@ import { z } from "zod"; export const TokenGenerationReadModelDbConfig = z .object({ - TOKEN_GENERATION_READMODEL_HOST: z.string().optional(), - TOKEN_GENERATION_READMODEL_PORT: z.coerce.number().min(1001).optional(), 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, tokenGenerationReadModelTableNamePlatform: c.TOKEN_GENERATION_READMODEL_TABLE_NAME_PLATFORM, tokenGenerationReadModelTableNameTokenGeneration: 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 eb774d21fc..3c0c507435 100644 --- a/packages/models/src/token-generation-readmodel/platform-states-entry.ts +++ b/packages/models/src/token-generation-readmodel/platform-states-entry.ts @@ -31,7 +31,7 @@ type PlatformStatesBaseEntry = z.infer; export const PlatformStatesCatalogEntry = PlatformStatesBaseEntry.extend({ PK: PlatformStatesEServiceDescriptorPK, - descriptorAudience: z.string(), + descriptorAudience: z.array(z.string()), descriptorVoucherLifespan: z.number(), }); export type PlatformStatesCatalogEntry = 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 56b8d3c891..f2c70708e3 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 @@ -34,7 +34,7 @@ export const TokenGenerationStatesClientPurposeEntry = agreementState: ItemState.optional(), GSIPK_eserviceId_descriptorId: GSIPKEServiceIdDescriptorId.optional(), descriptorState: ItemState.optional(), - descriptorAudience: z.string().optional(), + descriptorAudience: z.array(z.string()).optional(), descriptorVoucherLifespan: z.number().optional(), GSIPK_purposeId: PurposeId.optional(), purposeState: ItemState.optional(), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b0e03f4a8b..5f405fecef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1286,9 +1286,15 @@ 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 '@aws-sdk/client-sesv2': specifier: 3.620.1 version: 3.620.1 + '@aws-sdk/util-dynamodb': + specifier: 3.658.1 + version: 3.658.1(@aws-sdk/client-dynamodb@3.637.0) '@pagopa/eslint-config': specifier: 3.0.0 version: 3.0.0(typescript@5.4.5) @@ -1525,7 +1531,7 @@ importers: dependencies: aws-msk-iam-sasl-signer-js: specifier: 1.0.0 - version: 1.0.0(@aws-sdk/client-sso-oidc@3.637.0) + version: 1.0.0(@aws-sdk/client-sso-oidc@3.645.0) kafkajs: specifier: 2.2.4 version: 2.2.4 @@ -2567,7 +2573,7 @@ packages: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 '@aws-sdk/client-sso-oidc': 3.600.0(@aws-sdk/client-sts@3.600.0) - '@aws-sdk/client-sts': 3.600.0(@aws-sdk/client-sso-oidc@3.600.0) + '@aws-sdk/client-sts': 3.600.0 '@aws-sdk/core': 3.598.0 '@aws-sdk/credential-provider-node': 3.600.0(@aws-sdk/client-sso-oidc@3.600.0)(@aws-sdk/client-sts@3.600.0) '@aws-sdk/middleware-endpoint-discovery': 3.598.0 @@ -2662,7 +2668,6 @@ packages: uuid: 9.0.1 transitivePeerDependencies: - aws-crt - dev: false /@aws-sdk/client-dynamodb@3.648.0: resolution: {integrity: sha512-61yU6wQRlwOhD0mfJS/N8SYmv9hxkVYGKsXqSJ5PNNnySutoNof7cmX8cTuijpTQqLL9sKPfvPMlJCv7/M1AiA==} @@ -2723,7 +2728,7 @@ packages: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 '@aws-sdk/client-sso-oidc': 3.600.0(@aws-sdk/client-sts@3.600.0) - '@aws-sdk/client-sts': 3.600.0(@aws-sdk/client-sso-oidc@3.600.0) + '@aws-sdk/client-sts': 3.600.0 '@aws-sdk/core': 3.598.0 '@aws-sdk/credential-provider-node': 3.600.0(@aws-sdk/client-sso-oidc@3.600.0)(@aws-sdk/client-sts@3.600.0) '@aws-sdk/middleware-host-header': 3.598.0 @@ -2773,7 +2778,7 @@ packages: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 '@aws-sdk/client-sso-oidc': 3.600.0(@aws-sdk/client-sts@3.600.0) - '@aws-sdk/client-sts': 3.600.0(@aws-sdk/client-sso-oidc@3.600.0) + '@aws-sdk/client-sts': 3.600.0 '@aws-sdk/core': 3.598.0 '@aws-sdk/credential-provider-node': 3.600.0(@aws-sdk/client-sso-oidc@3.600.0)(@aws-sdk/client-sts@3.600.0) '@aws-sdk/middleware-bucket-endpoint': 3.598.0 @@ -2886,7 +2891,7 @@ packages: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 '@aws-sdk/client-sso-oidc': 3.600.0(@aws-sdk/client-sts@3.600.0) - '@aws-sdk/client-sts': 3.600.0(@aws-sdk/client-sso-oidc@3.600.0) + '@aws-sdk/client-sts': 3.600.0 '@aws-sdk/core': 3.598.0 '@aws-sdk/credential-provider-node': 3.600.0(@aws-sdk/client-sso-oidc@3.600.0)(@aws-sdk/client-sts@3.600.0) '@aws-sdk/middleware-host-header': 3.598.0 @@ -2936,7 +2941,7 @@ packages: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sts': 3.600.0(@aws-sdk/client-sso-oidc@3.600.0) + '@aws-sdk/client-sts': 3.600.0 '@aws-sdk/core': 3.598.0 '@aws-sdk/credential-provider-node': 3.600.0(@aws-sdk/client-sso-oidc@3.600.0)(@aws-sdk/client-sts@3.600.0) '@aws-sdk/middleware-host-header': 3.598.0 @@ -3078,7 +3083,7 @@ packages: transitivePeerDependencies: - aws-crt - /@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.609.0): + /@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: @@ -3086,9 +3091,9 @@ packages: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sts': 3.609.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.609.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 @@ -3126,26 +3131,25 @@ 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==} + /@aws-sdk/client-sso-oidc@3.645.0(@aws-sdk/client-sts@3.609.0): + resolution: {integrity: sha512-X9ULtdk3cO+1ysurEkJ1MSnu6U00qodXx+IVual+1jXX4RYY1WmQmfo7uDKf6FFkz7wW1DAqU+GJIBNQr0YH8A==} engines: {node: '>=16.0.0'} peerDependencies: - '@aws-sdk/client-sts': ^3.637.0 + '@aws-sdk/client-sts': ^3.645.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/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.637.0) + '@aws-sdk/credential-provider-node': 3.645.0(@aws-sdk/client-sso-oidc@3.645.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/middleware-user-agent': 3.645.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-endpoints': 3.645.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 @@ -3409,7 +3413,6 @@ packages: tslib: 2.6.3 transitivePeerDependencies: - aws-crt - dev: false /@aws-sdk/client-sso@3.645.0: resolution: {integrity: sha512-2rc8TjnsNddOeKQ/pfNN7deNvGLXAeKeYtHtGDAiM2qfTKxd2sNcAsZ+JCDLyshuD4xLM5fpUyR0X8As9EAouQ==} @@ -3457,7 +3460,7 @@ packages: - aws-crt dev: false - /@aws-sdk/client-sts@3.600.0(@aws-sdk/client-sso-oidc@3.600.0): + /@aws-sdk/client-sts@3.600.0: resolution: {integrity: sha512-KQG97B7LvTtTiGmjlrG1LRAY8wUvCQzrmZVV5bjrJ/1oXAU7DITYwVbSJeX9NWg6hDuSk0VE3MFwIXS2SvfLIA==} engines: {node: '>=16.0.0'} dependencies: @@ -3502,7 +3505,6 @@ packages: '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - aws-crt dev: false @@ -3647,7 +3649,6 @@ packages: tslib: 2.6.3 transitivePeerDependencies: - aws-crt - dev: false /@aws-sdk/client-sts@3.645.0: resolution: {integrity: sha512-6azXYtvtnAsPf2ShN9vKynIYVcJOpo6IoVmoMAVgNaBJyllP+s/RORzranYZzckqfmrudSxtct4rVapjLWuAMg==} @@ -3751,7 +3752,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==} @@ -3852,7 +3852,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==} @@ -3860,7 +3859,7 @@ packages: peerDependencies: '@aws-sdk/client-sts': ^3.598.0 dependencies: - '@aws-sdk/client-sts': 3.600.0(@aws-sdk/client-sso-oidc@3.600.0) + '@aws-sdk/client-sts': 3.600.0 '@aws-sdk/credential-provider-env': 3.598.0 '@aws-sdk/credential-provider-http': 3.598.0 '@aws-sdk/credential-provider-process': 3.598.0 @@ -3890,9 +3889,9 @@ 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: @@ -3900,7 +3899,7 @@ 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): + /@aws-sdk/credential-provider-ini@3.609.0(@aws-sdk/client-sso-oidc@3.645.0)(@aws-sdk/client-sts@3.609.0): resolution: {integrity: sha512-hwaBfXuBTv6/eAdEsDfGcteYUW6Km7lvvubbxEdxIuJNF3vswR7RMGIXaEC37hhPkTTgd3H0TONammhwZIfkog==} engines: {node: '>=16.0.0'} peerDependencies: @@ -3910,12 +3909,12 @@ packages: '@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-sso': 3.609.0(@aws-sdk/client-sso-oidc@3.645.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: @@ -3945,18 +3944,18 @@ packages: - '@aws-sdk/client-sso-oidc' - aws-crt - /@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-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.609.0 + '@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.609.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 @@ -3966,20 +3965,19 @@ 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==} + /@aws-sdk/credential-provider-ini@3.645.0(@aws-sdk/client-sso-oidc@3.645.0)(@aws-sdk/client-sts@3.609.0): + resolution: {integrity: sha512-LlZW0qwUwNlTaAIDCNpLbPsyXvS42pRIwF92fgtCQedmdnpN3XRUC6hcwSYI7Xru3GGKp3RnceOvsdOaRJORsw==} engines: {node: '>=16.0.0'} peerDependencies: - '@aws-sdk/client-sts': ^3.637.0 + '@aws-sdk/client-sts': ^3.645.0 dependencies: - '@aws-sdk/client-sts': 3.637.0 + '@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.637.0) + '@aws-sdk/credential-provider-sso': 3.645.0(@aws-sdk/client-sso-oidc@3.645.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 @@ -4047,9 +4045,9 @@ 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: @@ -4058,20 +4056,20 @@ 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): + /@aws-sdk/credential-provider-node@3.609.0(@aws-sdk/client-sso-oidc@3.645.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-ini': 3.609.0(@aws-sdk/client-sso-oidc@3.645.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-sso': 3.609.0(@aws-sdk/client-sso-oidc@3.645.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: @@ -4101,16 +4099,16 @@ packages: - '@aws-sdk/client-sts' - aws-crt - /@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/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.609.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.609.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 @@ -4121,18 +4119,17 @@ 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==} + /@aws-sdk/credential-provider-node@3.645.0(@aws-sdk/client-sso-oidc@3.645.0)(@aws-sdk/client-sts@3.609.0): + resolution: {integrity: sha512-eGFFuNvLeXjCJf5OCIuSEflxUowmK+bCS+lK4M8ofsYOEGAivdx7C0UPxNjHpvM8wKd8vpMl5phTeS9BWX5jMQ==} 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-ini': 3.645.0(@aws-sdk/client-sso-oidc@3.645.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.637.0) + '@aws-sdk/credential-provider-sso': 3.645.0(@aws-sdk/client-sso-oidc@3.645.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 @@ -4184,7 +4181,7 @@ 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 @@ -4223,7 +4220,7 @@ 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: @@ -4231,15 +4228,15 @@ packages: - aws-crt dev: false - /@aws-sdk/credential-provider-sso@3.609.0(@aws-sdk/client-sso-oidc@3.637.0): + /@aws-sdk/credential-provider-sso@3.609.0(@aws-sdk/client-sso-oidc@3.645.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/token-providers': 3.609.0(@aws-sdk/client-sso-oidc@3.645.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: @@ -4276,7 +4273,6 @@ packages: transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt - dev: false /@aws-sdk/credential-provider-sso@3.645.0(@aws-sdk/client-sso-oidc@3.645.0): resolution: {integrity: sha512-d6XuChAl5NCsCrUexc6AFb4efPmb9+66iwPylKG+iMTMYgO1ackfy1Q2/f35jdn0jolkPkzKsVyfzsEVoID6ew==} @@ -4300,7 +4296,7 @@ packages: peerDependencies: '@aws-sdk/client-sts': ^3.598.0 dependencies: - '@aws-sdk/client-sts': 3.600.0(@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/types': 3.3.0 @@ -4356,7 +4352,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.645.0): resolution: {integrity: sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==} @@ -4371,7 +4366,7 @@ packages: tslib: 2.6.3 dev: false - /@aws-sdk/credential-providers@3.609.0(@aws-sdk/client-sso-oidc@3.637.0): + /@aws-sdk/credential-providers@3.609.0(@aws-sdk/client-sso-oidc@3.645.0): resolution: {integrity: sha512-bJKMY4QwRVderh8R2s9kukoZhuNZew/xzwPa9DRRFVOIsznsS0faAdmAAFrKb8e06YyQq6DiZP0BfFyVHAXE2A==} engines: {node: '>=16.0.0'} dependencies: @@ -4381,10 +4376,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.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-ini': 3.609.0(@aws-sdk/client-sso-oidc@3.645.0)(@aws-sdk/client-sts@3.609.0) + '@aws-sdk/credential-provider-node': 3.609.0(@aws-sdk/client-sso-oidc@3.645.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-sso': 3.609.0(@aws-sdk/client-sso-oidc@3.645.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 @@ -4402,7 +4397,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==} @@ -4439,7 +4433,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==} @@ -4656,7 +4649,6 @@ packages: '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 tslib: 2.6.3 - dev: false /@aws-sdk/middleware-user-agent@3.645.0: resolution: {integrity: sha512-NpTAtqWK+49lRuxfz7st9for80r4NriCMK0RfdJSoPFVntjsSQiQ7+2nW2XL05uVY633e9DvCAw8YatX3zd1mw==} @@ -4770,13 +4762,13 @@ packages: tslib: 2.6.3 dev: false - /@aws-sdk/token-providers@3.609.0(@aws-sdk/client-sso-oidc@3.637.0): + /@aws-sdk/token-providers@3.609.0(@aws-sdk/client-sso-oidc@3.645.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/client-sso-oidc': 3.645.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 @@ -4803,13 +4795,12 @@ packages: 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/client-sso-oidc': 3.637.0(@aws-sdk/client-sts@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 - dev: false /@aws-sdk/token-providers@3.614.0(@aws-sdk/client-sso-oidc@3.645.0): resolution: {integrity: sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==} @@ -4817,7 +4808,7 @@ packages: peerDependencies: '@aws-sdk/client-sso-oidc': ^3.614.0 dependencies: - '@aws-sdk/client-sso-oidc': 3.645.0(@aws-sdk/client-sts@3.645.0) + '@aws-sdk/client-sso-oidc': 3.645.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 @@ -4877,6 +4868,16 @@ packages: tslib: 2.6.3 dev: false + /@aws-sdk/util-dynamodb@3.658.1(@aws-sdk/client-dynamodb@3.637.0): + resolution: {integrity: sha512-lzlnis+35a2OhGZlVJvM3/30iIVoP2cIv5Bkw1F2nkM6Pr+1NOd3XvYhCY1Ud5zWtV6HUSptzessvUPqJTMfjQ==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-dynamodb': ^3.658.1 + dependencies: + '@aws-sdk/client-dynamodb': 3.637.0 + tslib: 2.6.3 + dev: true + /@aws-sdk/util-endpoints@3.598.0: resolution: {integrity: sha512-Qo9UoiVVZxcOEdiOMZg3xb1mzkTxrhd4qSlg5QQrfWPJVx/QOg+Iy0NtGxPtHtVZNHZxohYwDwV/tfsnDSE2gQ==} engines: {node: '>=16.0.0'} @@ -4914,7 +4915,6 @@ packages: '@smithy/types': 3.3.0 '@smithy/util-endpoints': 2.0.5 tslib: 2.6.3 - dev: false /@aws-sdk/util-endpoints@3.645.0: resolution: {integrity: sha512-Oe+xaU4ic4PB1k3pb5VTC1/MWES13IlgpaQw01bVHGfwP6Yv6zZOxizRzca2Y3E+AyR+nKD7vXtHRY+w3bi4bg==} @@ -6570,7 +6570,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==} @@ -7322,13 +7321,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.637.0): + /aws-msk-iam-sasl-signer-js@1.0.0(@aws-sdk/client-sso-oidc@3.645.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.637.0) + '@aws-sdk/credential-providers': 3.609.0(@aws-sdk/client-sso-oidc@3.645.0) '@aws-sdk/util-format-url': 3.609.0 '@smithy/signature-v4': 2.3.0 '@types/buffers': 0.1.31 @@ -8725,7 +8724,6 @@ packages: hasBin: true dependencies: strnum: 1.0.5 - dev: false /fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} @@ -9886,7 +9884,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==} @@ -10095,7 +10092,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==}