Skip to content

Commit abe9c61

Browse files
committed
Added e2e test and fixed bugs in handling
1 parent 52ac6c5 commit abe9c61

File tree

8 files changed

+155
-18
lines changed

8 files changed

+155
-18
lines changed
Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import { Response } from 'express';
2-
export function sendCreated(response: Response, createdId: string): void {
3-
response.setHeader('Location', `/cash-registers/${createdId}`);
2+
export function sendCreated(
3+
response: Response,
4+
createdId: string,
5+
urlPrefix?: string
6+
): void {
7+
response.setHeader(
8+
'Location',
9+
`${urlPrefix ?? response.req.url}/${createdId}`
10+
);
411
response.status(201).json({ id: createdId });
512
}

samples/optimisticConcurrency/src/shoppingCarts/addingProductItem/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ function mapRequestToCommand(
6565
return 'MISSING_SHOPPING_CARD_ID';
6666
}
6767

68-
if (!isNotEmptyString(request.params.productId)) {
68+
if (!isNotEmptyString(request.body.productId)) {
6969
return 'MISSING_PRODUCT_ID';
7070
}
7171

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import request from 'supertest';
2+
import { v4 as uuid } from 'uuid';
3+
import { config } from '#config';
4+
import { Subscription } from '#core/eventStore/subscribing';
5+
import {
6+
EventStoreDBContainer,
7+
StartedEventStoreDBContainer,
8+
} from '#testing/eventStoreDB/eventStoreDBContainer';
9+
import {
10+
MongoDBContainer,
11+
StartedMongoDBContainer,
12+
} from '#testing/mongoDB/mongoDBContainer';
13+
import { retry } from '#testing/retries';
14+
import app from '../../app';
15+
import { getSubscription } from '../../getSubscription';
16+
17+
describe('Full flow', () => {
18+
let esdbContainer: StartedEventStoreDBContainer;
19+
let mongodbContainer: StartedMongoDBContainer;
20+
let subscription: Subscription;
21+
const clientId = uuid();
22+
23+
beforeAll(async () => {
24+
esdbContainer = await new EventStoreDBContainer().startContainer();
25+
config.eventStoreDB.connectionString = esdbContainer.getConnectionString();
26+
27+
mongodbContainer = await new MongoDBContainer().startContainer();
28+
config.mongoDB.connectionString = mongodbContainer.getConnectionString();
29+
console.log(config.mongoDB.connectionString);
30+
31+
const subscriptionResult = getSubscription();
32+
33+
if (subscriptionResult.isError) {
34+
console.error(subscriptionResult.error);
35+
return;
36+
}
37+
38+
subscription = subscriptionResult.value;
39+
subscription.subscribe();
40+
});
41+
42+
afterAll(async () => {
43+
try {
44+
await subscription.unsubscribe();
45+
} catch (err) {
46+
console.warn(`Failed to unsubscribe: ${err}`);
47+
}
48+
await esdbContainer.stop();
49+
await mongodbContainer.stop();
50+
});
51+
52+
describe('Shopping Cart', () => {
53+
let shoppingCartId: string;
54+
let currentRevision: string;
55+
let firstProductId: string = uuid();
56+
57+
it("should open when it wasn't open yet", async () => {
58+
await request(app)
59+
.post(`/clients/${clientId}/shopping-carts`)
60+
.expect(201)
61+
.expect('Content-Type', /json/)
62+
.then(async (response) => {
63+
expect(response.body).toHaveProperty('id');
64+
expect(response.headers['etag']).toBeDefined();
65+
expect(response.headers['etag']).toMatch(/W\/"\d+.*"/);
66+
67+
expect(response.headers['location']).toBeDefined();
68+
expect(response.headers['location']).toBe(
69+
`/clients/${clientId}/shopping-carts/${response.body.id}`
70+
);
71+
72+
currentRevision = response.headers['etag'];
73+
shoppingCartId = response.body.id;
74+
});
75+
76+
await request(app)
77+
.post(
78+
`/clients/${clientId}/shopping-carts/${shoppingCartId}/product-items`
79+
)
80+
.set('If-Match', currentRevision)
81+
.send({ productId: firstProductId, quantity: 10 })
82+
.expect(200)
83+
.expect('Content-Type', /plain/)
84+
.then(async (response) => {
85+
expect(response.headers['etag']).toBeDefined();
86+
expect(response.headers['etag']).toMatch(/W\/"\d+.*"/);
87+
88+
currentRevision = response.headers['etag'];
89+
});
90+
91+
await request(app)
92+
.delete(
93+
`/clients/${clientId}/shopping-carts/${shoppingCartId}/product-items`
94+
)
95+
.set('If-Match', currentRevision)
96+
.send({ productId: firstProductId, quantity: 5 })
97+
.expect(200)
98+
.expect('Content-Type', /plain/)
99+
.then(async (response) => {
100+
expect(response.headers['etag']).toBeDefined();
101+
expect(response.headers['etag']).toMatch(/W\/"\d+.*"/);
102+
103+
currentRevision = response.headers['etag'];
104+
});
105+
106+
await retry(() =>
107+
request(app)
108+
.get(`/clients/${clientId}/shopping-carts/${shoppingCartId}`)
109+
.expect(200)
110+
.expect('Content-Type', /json/)
111+
);
112+
});
113+
});
114+
});
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import { Router } from 'express';
2-
import { route as routeConfirmShoppingCart } from './opening';
2+
import { route as routeOpenShoppingCart } from './opening';
3+
import { route as routeAddProductItem } from './addingProductItem';
4+
import { route as routeRemoveProductItem } from './removingProductItem';
5+
import { route as routeConfirmShoppingCart } from './confirming';
6+
import { route as routeGetShoppingCartById } from './gettingById';
37

48
export const shoppingCartRouter = Router();
59

10+
routeOpenShoppingCart(shoppingCartRouter);
11+
routeAddProductItem(shoppingCartRouter);
12+
routeRemoveProductItem(shoppingCartRouter);
613
routeConfirmShoppingCart(shoppingCartRouter);
14+
routeGetShoppingCartById(shoppingCartRouter);
715
export * from './shoppingCart';

samples/optimisticConcurrency/src/shoppingCarts/opening/route.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { NextFunction, Request, Response, Router } from 'express';
2+
import { v4 as uuid } from 'uuid';
23
import { isCommand } from '#core/commands';
34
import { openShoppingCart, OpenShoppingCart } from './handler';
45
import { getShoppingCartStreamName } from '../shoppingCart';
@@ -10,7 +11,7 @@ import { sendCreated } from '#core/http/responses';
1011

1112
export const route = (router: Router) =>
1213
router.post(
13-
'/clients/:clientId/shopping-carts/:shoppingCartId',
14+
'/clients/:clientId/shopping-carts/',
1415
async function (request: Request, response: Response, next: NextFunction) {
1516
try {
1617
const command = mapRequestToCommand(request);
@@ -48,15 +49,12 @@ function mapRequestToCommand(
4849
if (!isNotEmptyString(request.params.clientId)) {
4950
return 'MISSING_CLIENT_ID';
5051
}
51-
if (!isNotEmptyString(request.params.shoppingCartId)) {
52-
return 'MISSING_SHOPPING_CARD_ID';
53-
}
5452

5553
return {
5654
type: 'open-shopping-cart',
5755
data: {
5856
clientId: request.body.clientId,
59-
shoppingCartId: request.body.shoppingCartId,
57+
shoppingCartId: uuid(),
6058
},
6159
};
6260
}

samples/optimisticConcurrency/src/shoppingCarts/removingProductItem/route.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ function mapRequestToCommand(
6666
return 'MISSING_SHOPPING_CARD_ID';
6767
}
6868

69-
if (!isNotEmptyString(request.params.productId)) {
69+
if (!isNotEmptyString(request.body.productId)) {
7070
return 'MISSING_PRODUCT_ID';
7171
}
7272

@@ -83,9 +83,9 @@ function mapRequestToCommand(
8383
return {
8484
type: 'remove-product-item-from-shopping-cart',
8585
data: {
86-
shoppingCartId: request.body.shoppingCartId,
86+
shoppingCartId: request.params.shoppingCartId,
8787
productItem: {
88-
productId: request.params.productId,
88+
productId: request.body.productId,
8989
quantity: request.body.quantity,
9090
},
9191
},

samples/optimisticConcurrency/src/testing/eventStoreDB/eventStoreDBContainer.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,18 @@ export class EventStoreDBContainer extends GenericContainer {
5454
export class StartedEventStoreDBContainer {
5555
constructor(private container: StartedTestContainer) {}
5656

57-
stop(
57+
async stop(
5858
options?: Partial<{
5959
timeout: number;
6060
removeVolumes: boolean;
6161
}>
62-
): Promise<StoppedTestContainer> {
63-
return this.container.stop(options);
62+
): Promise<StoppedTestContainer | undefined> {
63+
try {
64+
return await this.container.stop(options);
65+
} catch (err) {
66+
console.warn(`Failed to stop EventStoreDB container: ${err}`);
67+
return undefined;
68+
}
6469
}
6570

6671
getConnectionString(): string {

samples/optimisticConcurrency/src/testing/mongoDB/mongoDBContainer.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,18 @@ export class MongoDBContainer extends GenericContainer {
3232
export class StartedMongoDBContainer {
3333
constructor(private container: StartedTestContainer) {}
3434

35-
stop(
35+
async stop(
3636
options?: Partial<{
3737
timeout: number;
3838
removeVolumes: boolean;
3939
}>
40-
): Promise<StoppedTestContainer> {
41-
return this.container.stop(options);
40+
): Promise<StoppedTestContainer | undefined> {
41+
try {
42+
return await this.container.stop(options);
43+
} catch (err) {
44+
console.warn(`Failed to stop MongoDB container: ${err}`);
45+
return undefined;
46+
}
4247
}
4348

4449
getConnectionString(): string {

0 commit comments

Comments
 (0)