From a189e7b13f68db2e8286f55127b0065fd33f386c Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Tue, 19 Nov 2024 10:51:25 +0100 Subject: [PATCH] Added test reproducing issue with documentExists check It seems that the issue comes from the fact that Pongo doesn't deserialize dates --- .../expressjs-with-postgresql/package.json | 6 +- .../getDetails/index.int.spec.ts | 2 +- .../src/eventStore/commandHandler.int.spec.ts | 5 +- .../postgreSQLEventStore.e2e.spec.ts | 5 +- .../projections/pongo/pongoProjectionSpec.ts | 7 + .../postgresProjection.multi.int.spec.ts | 6 + .../postgresProjection.single.int.spec.ts | 205 +++++++++--------- .../src/testing/shoppingCart.domain.ts | 2 +- 8 files changed, 131 insertions(+), 107 deletions(-) diff --git a/samples/webApi/expressjs-with-postgresql/package.json b/samples/webApi/expressjs-with-postgresql/package.json index b979ebcb..adae163b 100644 --- a/samples/webApi/expressjs-with-postgresql/package.json +++ b/samples/webApi/expressjs-with-postgresql/package.json @@ -38,9 +38,9 @@ }, "homepage": "https://github.com/event-driven-io/emmett#readme", "dependencies": { - "@event-driven-io/emmett": "0.20.0", - "@event-driven-io/emmett-expressjs": "0.20.0", - "@event-driven-io/emmett-postgresql": "0.20.0" + "@event-driven-io/emmett": "0.21.0", + "@event-driven-io/emmett-expressjs": "0.21.0", + "@event-driven-io/emmett-postgresql": "0.21.0" }, "devDependencies": { "@testcontainers/postgresql": "^10.10.3", diff --git a/samples/webApi/expressjs-with-postgresql/src/shoppingCarts/getDetails/index.int.spec.ts b/samples/webApi/expressjs-with-postgresql/src/shoppingCarts/getDetails/index.int.spec.ts index 6c7dba52..032af620 100644 --- a/samples/webApi/expressjs-with-postgresql/src/shoppingCarts/getDetails/index.int.spec.ts +++ b/samples/webApi/expressjs-with-postgresql/src/shoppingCarts/getDetails/index.int.spec.ts @@ -47,7 +47,7 @@ void describe('Shopping Cart Short Details Projection', () => { } }); - void it.skip('adds product to empty shopping cart', () => + void it('adds product to empty shopping cart', () => given([]) .when([ { diff --git a/src/packages/emmett-postgresql/src/eventStore/commandHandler.int.spec.ts b/src/packages/emmett-postgresql/src/eventStore/commandHandler.int.spec.ts index f312b0b7..5c717c39 100644 --- a/src/packages/emmett-postgresql/src/eventStore/commandHandler.int.spec.ts +++ b/src/packages/emmett-postgresql/src/eventStore/commandHandler.int.spec.ts @@ -30,6 +30,7 @@ void describe('Postgres Projections', () => { let eventStore: PostgresEventStore; let connectionString: string; let pongo: PongoClient; + const now = new Date(); before(async () => { postgres = await new PostgreSqlContainer().start(); @@ -68,7 +69,7 @@ void describe('Postgres Projections', () => { const result = await handle(eventStore, shoppingCartId, () => ({ type: 'ProductItemAdded', - data: { productItem }, + data: { productItem, addedAt: now }, })); const couponId = uuid(); @@ -78,7 +79,7 @@ void describe('Postgres Projections', () => { await handle(eventStore, shoppingCartId, () => ({ type: 'ProductItemAdded', - data: { productItem }, + data: { productItem, addedAt: new Date() }, })); await handle(eventStore, shoppingCartId, () => ({ diff --git a/src/packages/emmett-postgresql/src/eventStore/postgreSQLEventStore.e2e.spec.ts b/src/packages/emmett-postgresql/src/eventStore/postgreSQLEventStore.e2e.spec.ts index b1e14bb6..a7ac70c4 100644 --- a/src/packages/emmett-postgresql/src/eventStore/postgreSQLEventStore.e2e.spec.ts +++ b/src/packages/emmett-postgresql/src/eventStore/postgreSQLEventStore.e2e.spec.ts @@ -30,6 +30,7 @@ void describe('EventStoreDBEventStore', () => { let eventStore: PostgresEventStore; let connectionString: string; let pongo: PongoClient; + const now = new Date(); before(async () => { postgres = await new PostgreSqlContainer().start(); @@ -65,10 +66,10 @@ void describe('EventStoreDBEventStore', () => { handledEventsInCustomProjection = []; await eventStore.appendToStream(shoppingCartId, [ - { type: 'ProductItemAdded', data: { productItem } }, + { type: 'ProductItemAdded', data: { productItem, addedAt: now } }, ]); await eventStore.appendToStream(shoppingCartId, [ - { type: 'ProductItemAdded', data: { productItem } }, + { type: 'ProductItemAdded', data: { productItem, addedAt: new Date() } }, ]); await eventStore.appendToStream(shoppingCartId, [ { diff --git a/src/packages/emmett-postgresql/src/eventStore/projections/pongo/pongoProjectionSpec.ts b/src/packages/emmett-postgresql/src/eventStore/projections/pongo/pongoProjectionSpec.ts index 6cdbb9ed..fe351bff 100644 --- a/src/packages/emmett-postgresql/src/eventStore/projections/pongo/pongoProjectionSpec.ts +++ b/src/packages/emmett-postgresql/src/eventStore/projections/pongo/pongoProjectionSpec.ts @@ -63,6 +63,13 @@ const assertDocumentsEqual = < // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `Document ids are not matching! Expected: ${expected._id}, actual: ${actual._id}`, ); + if ('_version' in expected) + assertEqual( + expected._version, + actual._version, + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Document versions are not matching! Expected: ${expected._version}, actual: ${actual._version}`, + ); return assertDeepEqual( withoutIdAndVersion(actual), diff --git a/src/packages/emmett-postgresql/src/eventStore/projections/postgresProjection.multi.int.spec.ts b/src/packages/emmett-postgresql/src/eventStore/projections/postgresProjection.multi.int.spec.ts index 43cd14b7..cd28a97a 100644 --- a/src/packages/emmett-postgresql/src/eventStore/projections/postgresProjection.multi.int.spec.ts +++ b/src/packages/emmett-postgresql/src/eventStore/projections/postgresProjection.multi.int.spec.ts @@ -57,6 +57,7 @@ void describe('Postgres Projections', () => { type: 'ProductItemAdded', data: { productItem: { price: 100, productId: 'shoes', quantity: 100 }, + addedAt: new Date(), }, metadata: { streamName: shoppingCartId, @@ -83,6 +84,7 @@ void describe('Postgres Projections', () => { type: 'ProductItemAdded', data: { productItem: { price: 100, productId: 'shoes', quantity: 100 }, + addedAt: new Date(), }, }), ]) @@ -107,6 +109,7 @@ void describe('Postgres Projections', () => { type: 'ProductItemAdded', data: { productItem: { price: 100, productId: 'shoes', quantity: 100 }, + addedAt: new Date(), }, }, ]), @@ -117,6 +120,7 @@ void describe('Postgres Projections', () => { type: 'ProductItemAdded', data: { productItem: { price: 30, productId: 'shoes', quantity: 30 }, + addedAt: new Date(), }, }, ]), @@ -141,6 +145,7 @@ void describe('Postgres Projections', () => { type: 'ProductItemAdded', data: { productItem: { price: 100, productId: 'shoes', quantity: 100 }, + addedAt: new Date(), }, }, ]), @@ -151,6 +156,7 @@ void describe('Postgres Projections', () => { type: 'ProductItemAdded', data: { productItem: { price: 100, productId: 'shoes', quantity: 100 }, + addedAt: new Date(), }, }, ]), diff --git a/src/packages/emmett-postgresql/src/eventStore/projections/postgresProjection.single.int.spec.ts b/src/packages/emmett-postgresql/src/eventStore/projections/postgresProjection.single.int.spec.ts index 30d782ee..72d05e70 100644 --- a/src/packages/emmett-postgresql/src/eventStore/projections/postgresProjection.single.int.spec.ts +++ b/src/packages/emmett-postgresql/src/eventStore/projections/postgresProjection.single.int.spec.ts @@ -1,3 +1,4 @@ +import { type ReadEvent } from '@event-driven-io/emmett/src'; import { PostgreSqlContainer, StartedPostgreSqlContainer, @@ -6,10 +7,6 @@ import { after, before, beforeEach, describe, it } from 'node:test'; import { v4 as uuid } from 'uuid'; import { documentExists, - eventInStream, - eventsInStream, - expectPongoDocuments, - newEventsInStream, pongoSingleStreamProjection, PostgreSQLProjectionSpec, } from '.'; @@ -23,6 +20,7 @@ void describe('Postgres Projections', () => { let connectionString: string; let given: PostgreSQLProjectionSpec; let shoppingCartId: string; + const now = new Date(); before(async () => { postgres = await new PostgreSqlContainer().start(); @@ -51,6 +49,7 @@ void describe('Postgres Projections', () => { type: 'ProductItemAdded', data: { productItem: { price: 100, productId: 'shoes', quantity: 100 }, + addedAt: now, }, metadata: { streamName: shoppingCartId, @@ -60,6 +59,7 @@ void describe('Postgres Projections', () => { .then( documentExists( { + openedAt: now, productItemsCount: 100, totalAmount: 10000, appliedDiscounts: [], @@ -71,102 +71,109 @@ void describe('Postgres Projections', () => { ), )); - void it('with empty given and when eventsInStream', () => - given([]) - .when([ - eventInStream(shoppingCartId, { - type: 'ProductItemAdded', - data: { - productItem: { price: 100, productId: 'shoes', quantity: 100 }, - }, - }), - ]) - .then( - expectPongoDocuments - .fromCollection( - shoppingCartShortInfoCollectionName, - ) - .withId(shoppingCartId) - .toBeEqual({ - productItemsCount: 100, - totalAmount: 10000, - appliedDiscounts: [], - }), - )); - - void it('with empty given and when eventsInStream', () => { - const couponId = uuid(); - - return given( - eventsInStream(shoppingCartId, [ - { - type: 'ProductItemAdded', - data: { - productItem: { price: 100, productId: 'shoes', quantity: 100 }, - }, - }, - ]), - ) - .when( - newEventsInStream(shoppingCartId, [ - { - type: 'DiscountApplied', - data: { percent: 10, couponId }, - }, - ]), - ) - .then( - expectPongoDocuments - .fromCollection( - shoppingCartShortInfoCollectionName, - ) - .withId(shoppingCartId) - .toBeEqual({ - productItemsCount: 100, - totalAmount: 9000, - appliedDiscounts: [couponId], - }), - ); - }); - - void it('with idempotency check', () => { - const couponId = uuid(); - - return given( - eventsInStream(shoppingCartId, [ - { - type: 'ProductItemAdded', - data: { - productItem: { price: 100, productId: 'shoes', quantity: 100 }, - }, - }, - ]), - ) - .when( - newEventsInStream(shoppingCartId, [ - { - type: 'DiscountApplied', - data: { percent: 10, couponId }, - }, - ]), - { numberOfTimes: 2 }, - ) - .then( - expectPongoDocuments - .fromCollection( - shoppingCartShortInfoCollectionName, - ) - .withId(shoppingCartId) - .toBeEqual({ - productItemsCount: 100, - totalAmount: 9000, - appliedDiscounts: [couponId], - }), - ); - }); + // void it('with empty given and when eventsInStream', () => + // given([]) + // .when([ + // eventInStream(shoppingCartId, { + // type: 'ProductItemAdded', + // data: { + // productItem: { price: 100, productId: 'shoes', quantity: 100 }, + // addedAt: now, + // }, + // }), + // ]) + // .then( + // expectPongoDocuments + // .fromCollection( + // shoppingCartShortInfoCollectionName, + // ) + // .withId(shoppingCartId) + // .toBeEqual({ + // openedAt: now, + // productItemsCount: 100, + // totalAmount: 10000, + // appliedDiscounts: [], + // }), + // )); + + // void it('with empty given and when eventsInStream', () => { + // const couponId = uuid(); + + // return given( + // eventsInStream(shoppingCartId, [ + // { + // type: 'ProductItemAdded', + // data: { + // productItem: { price: 100, productId: 'shoes', quantity: 100 }, + // addedAt: now, + // }, + // }, + // ]), + // ) + // .when( + // newEventsInStream(shoppingCartId, [ + // { + // type: 'DiscountApplied', + // data: { percent: 10, couponId }, + // }, + // ]), + // ) + // .then( + // expectPongoDocuments + // .fromCollection( + // shoppingCartShortInfoCollectionName, + // ) + // .withId(shoppingCartId) + // .toBeEqual({ + // openedAt: now, + // productItemsCount: 100, + // totalAmount: 9000, + // appliedDiscounts: [couponId], + // }), + // ); + // }); + + // void it('with idempotency check', () => { + // const couponId = uuid(); + + // return given( + // eventsInStream(shoppingCartId, [ + // { + // type: 'ProductItemAdded', + // data: { + // productItem: { price: 100, productId: 'shoes', quantity: 100 }, + // addedAt: now, + // }, + // }, + // ]), + // ) + // .when( + // newEventsInStream(shoppingCartId, [ + // { + // type: 'DiscountApplied', + // data: { percent: 10, couponId }, + // }, + // ]), + // { numberOfTimes: 2 }, + // ) + // .then( + // expectPongoDocuments + // .fromCollection( + // shoppingCartShortInfoCollectionName, + // ) + // .withId(shoppingCartId) + // .toBeEqual({ + // openedAt: now, + // productItemsCount: 100, + // totalAmount: 9000, + // appliedDiscounts: [couponId], + // }), + // ); + // }); }); type ShoppingCartShortInfo = { + openedAt: Date; productItemsCount: number; totalAmount: number; appliedDiscounts: string[]; @@ -176,12 +183,13 @@ const shoppingCartShortInfoCollectionName = 'shoppingCartShortInfo'; const evolve = ( document: ShoppingCartShortInfo, - { type, data: event }: ProductItemAdded | DiscountApplied, + { type, data: event }: ReadEvent, ): ShoppingCartShortInfo => { switch (type) { case 'ProductItemAdded': return { ...document, + openedAt: document.openedAt ?? event.addedAt, totalAmount: document.totalAmount + event.productItem.price * event.productItem.quantity, @@ -207,6 +215,7 @@ const shoppingCartShortInfoProjection = pongoSingleStreamProjection({ evolve, canHandle: ['ProductItemAdded', 'DiscountApplied'], initialState: () => ({ + openedAt: undefined!, productItemsCount: 0, totalAmount: 0, appliedDiscounts: [], diff --git a/src/packages/emmett-postgresql/src/testing/shoppingCart.domain.ts b/src/packages/emmett-postgresql/src/testing/shoppingCart.domain.ts index 816827d9..839c8191 100644 --- a/src/packages/emmett-postgresql/src/testing/shoppingCart.domain.ts +++ b/src/packages/emmett-postgresql/src/testing/shoppingCart.domain.ts @@ -13,7 +13,7 @@ export type ShoppingCart = { export type ProductItemAdded = Event< 'ProductItemAdded', - { productItem: PricedProductItem } + { productItem: PricedProductItem; addedAt: Date } >; export type DiscountApplied = Event< 'DiscountApplied',