@labdigital/commercetools-mock 2.61.2 → 2.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@labdigital/commercetools-mock",
3
- "version": "2.61.2",
3
+ "version": "2.62.0",
4
4
  "license": "MIT",
5
5
  "author": "Michael van Tellingen",
6
6
  "type": "module",
@@ -13,7 +13,7 @@
13
13
  },
14
14
  "main": "dist/index.mjs",
15
15
  "module": "dist/index.mjs",
16
- "typings": "dist/index.d.ts",
16
+ "typings": "dist/index.d.mts",
17
17
  "files": [
18
18
  "dist",
19
19
  "src"
@@ -1,3 +1,4 @@
1
+ import type { ShoppingListDraft } from "@commercetools/platform-sdk";
1
2
  import { describe, expect, test } from "vitest";
2
3
  import type { Config } from "#src/config.ts";
3
4
  import { InMemoryStorage } from "#src/storage/index.ts";
@@ -5,6 +6,7 @@ import {
5
6
  AsAssociateCartRepository,
6
7
  AsAssociateOrderRepository,
7
8
  AsAssociateQuoteRequestRepository,
9
+ AsAssociateShoppingListRepository,
8
10
  } from "./as-associate.ts";
9
11
  import { CustomerRepository } from "./customer/index.ts";
10
12
 
@@ -123,4 +125,27 @@ describe("As Associate Repositories", () => {
123
125
  expect(retrieved).toBeDefined();
124
126
  expect(retrieved?.id).toBe(quoteRequest.id);
125
127
  });
128
+
129
+ test("AsAssociateShoppingListRepository can create and retrieve shopping lists", () => {
130
+ const repository = new AsAssociateShoppingListRepository(config);
131
+ const ctx = { projectKey: "test-project" };
132
+
133
+ const shoppingListDraft: ShoppingListDraft = {
134
+ name: { "en-US": "My Shopping List" },
135
+ };
136
+
137
+ const shoppingList = repository.create(ctx, shoppingListDraft);
138
+ expect(shoppingList.id).toBeDefined();
139
+ expect(shoppingList.version).toBe(1);
140
+
141
+ // Test query
142
+ const result = repository.query(ctx);
143
+ expect(result.count).toBe(1);
144
+ expect(result.results[0].id).toBe(shoppingList.id);
145
+
146
+ // Test get
147
+ const retrieved = repository.get(ctx, shoppingList.id);
148
+ expect(retrieved).toBeDefined();
149
+ expect(retrieved?.id).toBe(shoppingList.id);
150
+ });
126
151
  });
@@ -1,7 +1,9 @@
1
1
  import { CartRepository } from "./cart/index.ts";
2
2
  import { OrderRepository } from "./order/index.ts";
3
3
  import { QuoteRequestRepository } from "./quote-request/index.ts";
4
+ import { ShoppingListRepository } from "./shopping-list/index.ts";
4
5
 
5
6
  export class AsAssociateOrderRepository extends OrderRepository {}
6
7
  export class AsAssociateCartRepository extends CartRepository {}
7
8
  export class AsAssociateQuoteRequestRepository extends QuoteRequestRepository {}
9
+ export class AsAssociateShoppingListRepository extends ShoppingListRepository {}
@@ -1,4 +1,6 @@
1
+ import assert from "node:assert";
1
2
  import type {
3
+ Address,
2
4
  Associate,
3
5
  BusinessUnit,
4
6
  BusinessUnitAddAddressAction,
@@ -32,8 +34,10 @@ import type {
32
34
  CompanyDraft,
33
35
  Division,
34
36
  DivisionDraft,
37
+ InvalidOperationError,
35
38
  } from "@commercetools/platform-sdk";
36
39
  import type { Config } from "#src/config.ts";
40
+ import { CommercetoolsError } from "#src/exceptions.ts";
37
41
  import { generateRandomString, getBaseResourceProperties } from "../helpers.ts";
38
42
  import type { Writable } from "../types.ts";
39
43
  import type { UpdateHandlerInterface } from "./abstract.ts";
@@ -201,22 +205,23 @@ class BusinessUnitUpdateHandler
201
205
  changeAddress(
202
206
  context: RepositoryContext,
203
207
  resource: Writable<BusinessUnit>,
204
- { addressId, address }: BusinessUnitChangeAddressAction,
208
+ { addressId, addressKey, address }: BusinessUnitChangeAddressAction,
205
209
  ) {
206
- const existingAddressIndex = resource.addresses.findIndex(
207
- (addr) => addr.id === addressId,
210
+ const current = this._findAddress(resource, addressId, addressKey, true);
211
+ assert(current?.id); // always true since we set required to true
212
+
213
+ const oldAddressIndex = resource.addresses.findIndex(
214
+ (a) => a.id === current.id,
208
215
  );
209
- if (existingAddressIndex === -1) {
210
- throw new Error(`Address with id ${addressId} not found`);
211
- }
212
216
 
213
217
  const newAddress = createAddress(
214
- { ...address, id: addressId },
218
+ { ...address, id: current.id },
215
219
  context.projectKey,
216
220
  this._storage,
217
221
  );
222
+
218
223
  if (newAddress) {
219
- resource.addresses[existingAddressIndex] = newAddress;
224
+ resource.addresses[oldAddressIndex] = newAddress;
220
225
  }
221
226
  }
222
227
 
@@ -344,35 +349,49 @@ class BusinessUnitUpdateHandler
344
349
  setDefaultShippingAddress(
345
350
  context: RepositoryContext,
346
351
  resource: Writable<BusinessUnit>,
347
- { addressId }: BusinessUnitSetDefaultShippingAddressAction,
352
+ { addressId, addressKey }: BusinessUnitSetDefaultShippingAddressAction,
348
353
  ) {
349
- resource.defaultShippingAddressId = addressId;
354
+ const address = this._findAddress(resource, addressId, addressKey, true);
355
+ assert(address?.id); // always true since we set required to true
356
+
357
+ resource.defaultShippingAddressId = address.id;
358
+ if (resource.shippingAddressIds === undefined) {
359
+ resource.shippingAddressIds = [];
360
+ }
361
+ if (!resource.shippingAddressIds.includes(address.id)) {
362
+ resource.shippingAddressIds.push(address.id);
363
+ }
350
364
  }
351
365
 
352
366
  addShippingAddressId(
353
367
  context: RepositoryContext,
354
368
  resource: Writable<BusinessUnit>,
355
- { addressId }: BusinessUnitAddShippingAddressIdAction,
369
+ { addressId, addressKey }: BusinessUnitAddShippingAddressIdAction,
356
370
  ) {
357
- if (!resource.shippingAddressIds) {
371
+ const address = this._findAddress(resource, addressId, addressKey, true);
372
+ assert(address?.id); // always true since we set required to true
373
+
374
+ if (resource.shippingAddressIds === undefined) {
358
375
  resource.shippingAddressIds = [];
359
376
  }
360
- if (addressId) {
361
- resource.shippingAddressIds.push(addressId);
377
+
378
+ if (!resource.shippingAddressIds.includes(address.id)) {
379
+ resource.shippingAddressIds.push(address.id);
362
380
  }
381
+ return resource;
363
382
  }
364
383
 
365
384
  removeShippingAddressId(
366
385
  context: RepositoryContext,
367
386
  resource: Writable<BusinessUnit>,
368
- { addressId }: BusinessUnitRemoveShippingAddressIdAction,
387
+ { addressId, addressKey }: BusinessUnitRemoveShippingAddressIdAction,
369
388
  ) {
370
- if (resource.shippingAddressIds) {
371
- resource.shippingAddressIds = resource.shippingAddressIds.filter(
372
- (id) => id !== addressId,
373
- );
374
- }
375
- if (resource.defaultShippingAddressId === addressId) {
389
+ const address = this._findAddress(resource, addressId, addressKey, true);
390
+ assert(address?.id); // always true since we set required to true
391
+ resource.shippingAddressIds = resource.shippingAddressIds?.filter(
392
+ (id) => id !== address.id,
393
+ );
394
+ if (resource.defaultShippingAddressId === address.id) {
376
395
  resource.defaultShippingAddressId = undefined;
377
396
  }
378
397
  }
@@ -380,27 +399,31 @@ class BusinessUnitUpdateHandler
380
399
  addBillingAddressId(
381
400
  context: RepositoryContext,
382
401
  resource: Writable<BusinessUnit>,
383
- { addressId }: BusinessUnitAddBillingAddressIdAction,
402
+ { addressId, addressKey }: BusinessUnitAddBillingAddressIdAction,
384
403
  ) {
385
- if (!resource.billingAddressIds) {
404
+ const address = this._findAddress(resource, addressId, addressKey, true);
405
+ assert(address?.id); // always true since we set required to true
406
+
407
+ if (resource.billingAddressIds === undefined) {
386
408
  resource.billingAddressIds = [];
387
409
  }
388
- if (addressId) {
389
- resource.billingAddressIds.push(addressId);
410
+
411
+ if (!resource.billingAddressIds.includes(address.id)) {
412
+ resource.billingAddressIds.push(address.id);
390
413
  }
391
414
  }
392
415
 
393
416
  removeBillingAddressId(
394
417
  context: RepositoryContext,
395
418
  resource: Writable<BusinessUnit>,
396
- { addressId }: BusinessUnitRemoveBillingAddressIdAction,
419
+ { addressId, addressKey }: BusinessUnitRemoveBillingAddressIdAction,
397
420
  ) {
398
- if (resource.billingAddressIds) {
399
- resource.billingAddressIds = resource.billingAddressIds.filter(
400
- (id) => id !== addressId,
401
- );
402
- }
403
- if (resource.defaultBillingAddressId === addressId) {
421
+ const address = this._findAddress(resource, addressId, addressKey, true);
422
+ assert(address?.id); // always true since we set required to true
423
+ resource.billingAddressIds = resource.billingAddressIds?.filter(
424
+ (id) => id !== address.id,
425
+ );
426
+ if (resource.defaultBillingAddressId === address.id) {
404
427
  resource.defaultBillingAddressId = undefined;
405
428
  }
406
429
  }
@@ -408,9 +431,18 @@ class BusinessUnitUpdateHandler
408
431
  setDefaultBillingAddress(
409
432
  context: RepositoryContext,
410
433
  resource: Writable<BusinessUnit>,
411
- { addressId }: BusinessUnitSetDefaultBillingAddressAction,
434
+ { addressId, addressKey }: BusinessUnitSetDefaultBillingAddressAction,
412
435
  ) {
413
- resource.defaultBillingAddressId = addressId;
436
+ const address = this._findAddress(resource, addressId, addressKey, true);
437
+ assert(address?.id); // always true since we set required to true
438
+
439
+ resource.defaultBillingAddressId = address.id;
440
+ if (resource.billingAddressIds === undefined) {
441
+ resource.billingAddressIds = [];
442
+ }
443
+ if (!resource.billingAddressIds.includes(address.id)) {
444
+ resource.billingAddressIds.push(address.id);
445
+ }
414
446
  }
415
447
 
416
448
  setCustomField(
@@ -467,28 +499,73 @@ class BusinessUnitUpdateHandler
467
499
  removeAddress(
468
500
  context: RepositoryContext,
469
501
  resource: Writable<BusinessUnit>,
470
- { addressId }: BusinessUnitRemoveAddressAction,
502
+ { addressId, addressKey }: BusinessUnitRemoveAddressAction,
471
503
  ) {
472
- resource.addresses = resource.addresses.filter(
473
- (addr) => addr.id !== addressId,
474
- );
504
+ const address = this._findAddress(resource, addressId, addressKey, true);
505
+ assert(address?.id); // always true since we set required to true
506
+ resource.addresses = resource.addresses.filter((a) => a.id !== address.id);
475
507
 
476
508
  if (resource.shippingAddressIds) {
477
509
  resource.shippingAddressIds = resource.shippingAddressIds.filter(
478
- (id) => id !== addressId,
510
+ (id) => id !== address.id,
479
511
  );
480
512
  }
481
513
  if (resource.billingAddressIds) {
482
514
  resource.billingAddressIds = resource.billingAddressIds.filter(
483
- (id) => id !== addressId,
515
+ (id) => id !== address.id,
484
516
  );
485
517
  }
486
518
 
487
- if (resource.defaultShippingAddressId === addressId) {
519
+ if (resource.defaultShippingAddressId === address.id) {
488
520
  resource.defaultShippingAddressId = undefined;
489
521
  }
490
- if (resource.defaultBillingAddressId === addressId) {
522
+ if (resource.defaultBillingAddressId === address.id) {
491
523
  resource.defaultBillingAddressId = undefined;
492
524
  }
493
525
  }
526
+
527
+ private _findAddress(
528
+ resource: Writable<BusinessUnit>,
529
+ addressId: string | undefined,
530
+ addressKey: string | undefined,
531
+ required = false,
532
+ ): Address | undefined {
533
+ if (addressKey) {
534
+ const address = resource.addresses.find((a) => a.key === addressKey);
535
+ if (!address) {
536
+ throw new CommercetoolsError<InvalidOperationError>(
537
+ {
538
+ code: "InvalidOperation",
539
+ message: `Business Unit does not contain an address with the key ${addressKey}.`,
540
+ },
541
+ 400,
542
+ );
543
+ }
544
+ return address;
545
+ }
546
+
547
+ if (addressId) {
548
+ const address = resource.addresses.find((a) => a.id === addressId);
549
+ if (!address) {
550
+ throw new CommercetoolsError<InvalidOperationError>(
551
+ {
552
+ code: "InvalidOperation",
553
+ message: `Business Unit does not contain an address with the id ${addressId}.`,
554
+ },
555
+ 400,
556
+ );
557
+ }
558
+ return address;
559
+ }
560
+
561
+ if (required) {
562
+ throw new CommercetoolsError<InvalidOperationError>(
563
+ {
564
+ code: "InvalidOperation",
565
+ message: "One of address 'addressId' or 'addressKey' is required.",
566
+ },
567
+ 400,
568
+ );
569
+ }
570
+ }
494
571
  }
@@ -149,6 +149,24 @@ export class CustomerUpdateHandler
149
149
  );
150
150
  assert(address?.id); // always true since we set required to true
151
151
  resource.addresses = resource.addresses.filter((a) => a.id !== address.id);
152
+
153
+ if (resource.shippingAddressIds) {
154
+ resource.shippingAddressIds = resource.shippingAddressIds.filter(
155
+ (id) => id !== address.id,
156
+ );
157
+ }
158
+ if (resource.billingAddressIds) {
159
+ resource.billingAddressIds = resource.billingAddressIds.filter(
160
+ (id) => id !== address.id,
161
+ );
162
+ }
163
+
164
+ if (resource.defaultShippingAddressId === address.id) {
165
+ resource.defaultShippingAddressId = undefined;
166
+ }
167
+ if (resource.defaultBillingAddressId === address.id) {
168
+ resource.defaultBillingAddressId = undefined;
169
+ }
152
170
  }
153
171
 
154
172
  removeBillingAddressId(
@@ -4,6 +4,7 @@ import {
4
4
  AsAssociateCartRepository,
5
5
  AsAssociateOrderRepository,
6
6
  AsAssociateQuoteRequestRepository,
7
+ AsAssociateShoppingListRepository,
7
8
  } from "./as-associate.ts";
8
9
  import { AssociateRoleRepository } from "./associate-role.ts";
9
10
  import { AttributeGroupRepository } from "./attribute-group.ts";
@@ -53,6 +54,7 @@ export const createRepositories = (config: Config) => ({
53
54
  cart: new AsAssociateCartRepository(config),
54
55
  order: new AsAssociateOrderRepository(config),
55
56
  "quote-request": new AsAssociateQuoteRequestRepository(config),
57
+ "shopping-list": new AsAssociateShoppingListRepository(config),
56
58
  },
57
59
  "associate-role": new AssociateRoleRepository(config),
58
60
  "attribute-group": new AttributeGroupRepository(config),
@@ -0,0 +1,58 @@
1
+ import supertest from "supertest";
2
+ import { describe, expect, test } from "vitest";
3
+ import { CommercetoolsMock } from "../index.ts";
4
+
5
+ const ctMock = new CommercetoolsMock();
6
+ const projectKey = "dummy";
7
+ const customerId = "5fac8fca-2484-4b14-a1d1-cfdce2f8d3c4";
8
+ const businessUnitKey = "test-business-unit";
9
+
10
+ describe("AsAssociateShoppingList", () => {
11
+ test("Create shopping list", async () => {
12
+ const draft = {
13
+ name: { en: "My list" },
14
+ };
15
+ const response = await supertest(ctMock.app)
16
+ .post(
17
+ `/${projectKey}/as-associate/${customerId}/in-business-unit/key=${businessUnitKey}/shopping-lists`,
18
+ )
19
+ .send(draft);
20
+
21
+ expect(response.status).toBe(201);
22
+ expect(response.body.id).toBeDefined();
23
+ });
24
+
25
+ test("Get shopping list", async () => {
26
+ const createResponse = await supertest(ctMock.app)
27
+ .post(
28
+ `/${projectKey}/as-associate/${customerId}/in-business-unit/key=${businessUnitKey}/shopping-lists`,
29
+ )
30
+ .send({ name: { en: "Groceries" } });
31
+
32
+ expect(createResponse.status).toBe(201);
33
+
34
+ const response = await supertest(ctMock.app).get(
35
+ `/${projectKey}/as-associate/${customerId}/in-business-unit/key=${businessUnitKey}/shopping-lists/${createResponse.body.id}`,
36
+ );
37
+
38
+ expect(response.status).toBe(200);
39
+ expect(response.body).toEqual(createResponse.body);
40
+ });
41
+
42
+ test("Query shopping lists", async () => {
43
+ const createResponse = await supertest(ctMock.app)
44
+ .post(
45
+ `/${projectKey}/as-associate/${customerId}/in-business-unit/key=${businessUnitKey}/shopping-lists`,
46
+ )
47
+ .send({ name: { en: "Errands" } });
48
+
49
+ expect(createResponse.status).toBe(201);
50
+
51
+ const response = await supertest(ctMock.app).get(
52
+ `/${projectKey}/as-associate/${customerId}/in-business-unit/key=${businessUnitKey}/shopping-lists`,
53
+ );
54
+
55
+ expect(response.status).toBe(200);
56
+ expect(response.body.count).toBeGreaterThan(0);
57
+ });
58
+ });
@@ -0,0 +1,33 @@
1
+ import { Router } from "express";
2
+ import type { ShoppingListRepository } from "#src/repositories/shopping-list/index.ts";
3
+ import AbstractService from "./abstract.ts";
4
+
5
+ export class AsAssociateShoppingListService extends AbstractService {
6
+ public repository: ShoppingListRepository;
7
+
8
+ constructor(parent: Router, repository: ShoppingListRepository) {
9
+ super(parent);
10
+ this.repository = repository;
11
+ }
12
+
13
+ getBasePath() {
14
+ return "shopping-lists";
15
+ }
16
+
17
+ registerRoutes(parent: Router) {
18
+ const basePath = this.getBasePath();
19
+ const router = Router({ mergeParams: true });
20
+
21
+ this.extraRoutes(router);
22
+
23
+ router.get("/", this.get.bind(this));
24
+ router.get("/:id", this.getWithId.bind(this));
25
+
26
+ router.delete("/:id", this.deleteWithId.bind(this));
27
+
28
+ router.post("/", this.post.bind(this));
29
+ router.post("/:id", this.postWithId.bind(this));
30
+
31
+ parent.use(`/${basePath}`, router);
32
+ }
33
+ }
@@ -3,15 +3,18 @@ import type {
3
3
  AsAssociateCartRepository,
4
4
  AsAssociateOrderRepository,
5
5
  AsAssociateQuoteRequestRepository,
6
+ AsAssociateShoppingListRepository,
6
7
  } from "#src/repositories/as-associate.ts";
7
8
  import { AsAssociateCartService } from "./as-associate-cart.ts";
8
9
  import { AsAssociateOrderService } from "./as-associate-order.ts";
9
10
  import { AsAssociateQuoteRequestService } from "./as-associate-quote-request.ts";
11
+ import { AsAssociateShoppingListService } from "./as-associate-shopping-list.ts";
10
12
 
11
13
  type Repositories = {
12
14
  cart: AsAssociateCartRepository;
13
15
  order: AsAssociateOrderRepository;
14
16
  "quote-request": AsAssociateQuoteRequestRepository;
17
+ "shopping-list": AsAssociateShoppingListRepository;
15
18
  };
16
19
 
17
20
  export class AsAssociateService {
@@ -21,6 +24,7 @@ export class AsAssociateService {
21
24
  cart: AsAssociateCartService;
22
25
  order: AsAssociateOrderService;
23
26
  "quote-request": AsAssociateQuoteRequestService;
27
+ "shopping-list": AsAssociateShoppingListService;
24
28
  };
25
29
 
26
30
  constructor(parent: Router, repositories: Repositories) {
@@ -33,6 +37,10 @@ export class AsAssociateService {
33
37
  this.router,
34
38
  repositories["quote-request"],
35
39
  ),
40
+ "shopping-list": new AsAssociateShoppingListService(
41
+ this.router,
42
+ repositories["shopping-list"],
43
+ ),
36
44
  };
37
45
  parent.use(
38
46
  "/as-associate/:associateId/in-business-unit/key=:businessUnitId",