@reactionary/source 0.0.40 → 0.0.42

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.
Files changed (63) hide show
  1. package/.env-template +8 -5
  2. package/README.md +41 -0
  3. package/core/src/client/client.ts +2 -0
  4. package/core/src/decorators/reactionary.decorator.ts +16 -0
  5. package/core/src/index.ts +5 -28
  6. package/core/src/providers/cart-payment.provider.ts +56 -0
  7. package/core/src/providers/cart.provider.ts +125 -1
  8. package/core/src/providers/index.ts +10 -0
  9. package/core/src/providers/price.provider.ts +1 -1
  10. package/core/src/providers/product.provider.ts +13 -1
  11. package/core/src/schemas/capabilities.schema.ts +1 -0
  12. package/core/src/schemas/models/cart.model.ts +16 -3
  13. package/core/src/schemas/models/identifiers.model.ts +43 -3
  14. package/core/src/schemas/models/identity.model.ts +22 -2
  15. package/core/src/schemas/models/index.ts +14 -0
  16. package/core/src/schemas/models/payment.model.ts +41 -0
  17. package/core/src/schemas/models/profile.model.ts +34 -0
  18. package/core/src/schemas/models/shipping-method.model.ts +14 -0
  19. package/core/src/schemas/mutations/cart-payment.mutation.ts +21 -0
  20. package/core/src/schemas/mutations/cart.mutation.ts +67 -8
  21. package/core/src/schemas/mutations/identity.mutation.ts +2 -1
  22. package/core/src/schemas/mutations/index.ts +9 -0
  23. package/core/src/schemas/queries/cart-payment.query.ts +12 -0
  24. package/core/src/schemas/queries/index.ts +1 -0
  25. package/examples/next/src/app/page.tsx +20 -15
  26. package/examples/node/package.json +3 -1
  27. package/examples/node/src/basic/basic-node-provider-model-extension.spec.ts +13 -5
  28. package/examples/node/src/basic/basic-node-provider-query-extension.spec.ts +11 -1
  29. package/examples/node/src/basic/basic-node-setup.spec.ts +7 -3
  30. package/examples/node/src/test-utils.ts +33 -0
  31. package/otel/src/trace-decorator.ts +6 -5
  32. package/package.json +4 -3
  33. package/providers/algolia/src/test/search.provider.spec.ts +28 -19
  34. package/providers/algolia/src/test/test-utils.ts +31 -0
  35. package/providers/commercetools/README.md +16 -0
  36. package/providers/commercetools/package.json +1 -1
  37. package/providers/commercetools/src/core/client.ts +31 -16
  38. package/providers/commercetools/src/core/initialize.ts +39 -6
  39. package/providers/commercetools/src/providers/cart-payment.provider.ts +192 -0
  40. package/providers/commercetools/src/providers/cart.provider.ts +414 -107
  41. package/providers/commercetools/src/providers/category.provider.ts +7 -4
  42. package/providers/commercetools/src/providers/identity.provider.ts +12 -4
  43. package/providers/commercetools/src/providers/inventory.provider.ts +10 -0
  44. package/providers/commercetools/src/providers/price.provider.ts +12 -5
  45. package/providers/commercetools/src/providers/product.provider.ts +23 -14
  46. package/providers/commercetools/src/providers/search.provider.ts +43 -15
  47. package/providers/commercetools/src/schema/capabilities.schema.ts +1 -0
  48. package/providers/commercetools/src/schema/commercetools.schema.ts +18 -0
  49. package/providers/commercetools/src/schema/configuration.schema.ts +2 -1
  50. package/providers/commercetools/src/test/cart-payment.provider.spec.ts +149 -0
  51. package/providers/commercetools/src/test/cart.provider.spec.ts +69 -10
  52. package/providers/commercetools/src/test/category.provider.spec.ts +1 -14
  53. package/providers/commercetools/src/test/product.provider.spec.ts +27 -0
  54. package/providers/commercetools/src/test/test-utils.ts +22 -7
  55. package/providers/fake/src/core/initialize.ts +6 -8
  56. package/providers/fake/src/providers/cart.provider.ts +47 -3
  57. package/providers/fake/src/providers/price.provider.ts +1 -1
  58. package/providers/fake/src/providers/product.provider.ts +3 -0
  59. package/providers/fake/src/test/test-utils.ts +7 -2
  60. package/trpc/src/integration.spec.ts +24 -23
  61. package/trpc/src/test-utils.ts +31 -0
  62. package/trpc/src/transparent-client.spec.ts +22 -26
  63. package/providers/algolia/src/test/product.provider.spec.ts +0 -18
@@ -40,7 +40,7 @@ export class CommercetoolsIdentityProvider<
40
40
 
41
41
  if (current.success) {
42
42
  current.data.meta = {
43
- cache: { hit: false, key: session.identity.id || 'anonymous' },
43
+ cache: { hit: false, key: session.identity.id.userId || 'anonymous' },
44
44
  placeholder: false
45
45
  };
46
46
  return current.data;
@@ -66,16 +66,24 @@ export class CommercetoolsIdentityProvider<
66
66
  const base = this.newModel();
67
67
 
68
68
  if (remote && remote.access_token) {
69
+ base.id = { userId: this.extractCustomerIdFromScopes(remote.scope) };
70
+
71
+ base.keyring = base.keyring.filter(x => x.service !== 'commercetools');
72
+ base.keyring.push({
73
+ service: 'commercetools',
74
+ token: remote.access_token,
75
+ issued: new Date(),
76
+ expiry: new Date(new Date().getTime() + 3600 * 1000),
77
+ });
69
78
  base.issued = new Date();
70
79
  base.expiry = new Date();
71
80
  base.expiry.setSeconds(base.expiry.getSeconds() + remote.expires_in);
72
- base.id = this.extractCustomerIdFromScopes(remote.scope);
73
81
  base.token = remote.access_token;
74
82
  base.type = 'Registered';
75
83
  }
76
84
 
77
85
  base.meta = {
78
- cache: { hit: false, key: base.id || 'anonymous' },
86
+ cache: { hit: false, key: base.id.userId || 'anonymous' },
79
87
  placeholder: false
80
88
  };
81
89
 
@@ -111,4 +119,4 @@ export class CommercetoolsIdentityProvider<
111
119
 
112
120
  return id || '';
113
121
  }
114
- }
122
+ }
@@ -25,6 +25,16 @@ export class CommercetoolsInventoryProvider<
25
25
  this.config = config;
26
26
  }
27
27
 
28
+ protected getClient(session: Session) {
29
+ const token = session.identity.keyring.find(x => x.service === 'commercetools')?.token;
30
+ const client = new CommercetoolsClient(this.config).getClient(
31
+ token
32
+ );
33
+ return client.withProjectKey({ projectKey: this.config.projectKey }).inventory();
34
+ }
35
+
36
+
37
+
28
38
  public override async getBySKU(
29
39
  payload: InventoryQuery,
30
40
  session: Session
@@ -1,4 +1,5 @@
1
- import { Price, PriceProvider, PriceQueryBySku, Session, Cache, Currency, TieredPriceSchema, MonetaryAmountSchema, TieredPrice } from '@reactionary/core';
1
+ import { Price, PriceProvider, Cache, Currency, TieredPriceSchema, TieredPrice } from '@reactionary/core';
2
+ import type { PriceQueryBySku, Session } from '@reactionary/core';
2
3
  import z from 'zod';
3
4
  import { CommercetoolsConfiguration } from '../schema/configuration.schema';
4
5
  import { CommercetoolsClient } from '../core/client';
@@ -14,11 +15,17 @@ export class CommercetoolsPriceProvider<
14
15
  this.config = config;
15
16
  }
16
17
 
17
- public getClient(session: Session) {
18
- return new CommercetoolsClient(this.config).getClient(session.identity?.token).withProjectKey({ projectKey: this.config.projectKey }).standalonePrices();
18
+
19
+ protected getClient(session: Session) {
20
+ const token = session.identity.keyring.find(x => x.service === 'commercetools')?.token;
21
+ const client = new CommercetoolsClient(this.config).getClient(
22
+ token
23
+ );
24
+ return client.withProjectKey({ projectKey: this.config.projectKey }).standalonePrices();
19
25
  }
20
26
 
21
27
 
28
+
22
29
  public override async getBySKUs(payload: PriceQueryBySku[], session: Session): Promise<T[]> {
23
30
 
24
31
  const client = this.getClient(session);
@@ -40,7 +47,7 @@ export class CommercetoolsPriceProvider<
40
47
  if (matched && matched.length > 0) {
41
48
  result.push(this.parseSingle(matched[0], session));
42
49
  } else {
43
- result.push(this.getEmptyPriceResult(p.sku.key, session.languageContext.currencyCode ));
50
+ result.push(this.createEmptyPriceResult(p.sku.key, session.languageContext.currencyCode ));
44
51
  }
45
52
  }
46
53
 
@@ -70,7 +77,7 @@ export class CommercetoolsPriceProvider<
70
77
  if (matched && matched.length > 0) {
71
78
  return this.parseSingle(matched[0], session);
72
79
  }
73
- return this.getEmptyPriceResult(payload.sku.key, session.languageContext.currencyCode );
80
+ return this.createEmptyPriceResult(payload.sku.key, session.languageContext.currencyCode );
74
81
  }
75
82
 
76
83
 
@@ -1,15 +1,14 @@
1
1
  import {
2
2
  ProductProvider,
3
3
  Product,
4
- ProductQueryById,
5
- ProductQueryBySlug,
6
- Session,
7
4
  Cache,
8
5
  } from '@reactionary/core';
9
6
  import { CommercetoolsClient } from '../core/client';
10
7
  import { z } from 'zod';
11
8
  import { CommercetoolsConfiguration } from '../schema/configuration.schema';
12
9
  import { ProductProjection } from '@commercetools/platform-sdk';
10
+ import { traced } from '@reactionary/otel';
11
+ import type { ProductQueryById, ProductQueryBySlug, Session } from '@reactionary/core';
13
12
 
14
13
  export class CommercetoolsProductProvider<
15
14
  T extends Product = Product
@@ -22,28 +21,38 @@ export class CommercetoolsProductProvider<
22
21
  this.config = config;
23
22
  }
24
23
 
25
- public getClient(session: Session) {
26
- return new CommercetoolsClient(this.config).getClient(session.identity?.token).withProjectKey({ projectKey: this.config.projectKey }).productProjections();
24
+ protected getClient(session: Session) {
25
+ const token = session.identity.keyring.find(x => x.service === 'commercetools')?.token;
26
+ const client = new CommercetoolsClient(this.config).getClient(
27
+ token
28
+ );
29
+ return client.withProjectKey({ projectKey: this.config.projectKey }).productProjections();
27
30
  }
28
31
 
32
+ @traced()
29
33
  public override async getById(
30
34
  payload: ProductQueryById,
31
35
  session: Session
32
36
  ): Promise<T> {
33
37
  const client = this.getClient(session);
34
38
 
35
- const remote = await client
36
- .withId({ ID: payload.id })
37
- .get()
38
- .execute();
39
+ try {
40
+ const remote = await client
41
+ .withId({ ID: payload.id })
42
+ .get()
43
+ .execute();
39
44
 
40
- return this.parseSingle(remote.body, session);
45
+ return this.parseSingle(remote.body, session);
46
+ } catch(error) {
47
+ return this.createEmptyProduct(payload.id);
48
+ }
41
49
  }
42
50
 
51
+ @traced()
43
52
  public override async getBySlug(
44
53
  payload: ProductQueryBySlug,
45
54
  session: Session
46
- ): Promise<T> {
55
+ ): Promise<T | null> {
47
56
  const client = this.getClient(session);
48
57
 
49
58
  const remote = await client
@@ -55,10 +64,9 @@ export class CommercetoolsProductProvider<
55
64
  })
56
65
  .execute();
57
66
 
58
- if (remote.body.results.length === 0) {
59
- throw new Error(`Product with slug '${payload.slug}' not found`);
67
+ if (remote.body.count === 0) {
68
+ return null;
60
69
  }
61
-
62
70
  return this.parseSingle(remote.body.results[0], session);
63
71
  }
64
72
 
@@ -66,6 +74,7 @@ export class CommercetoolsProductProvider<
66
74
  const data = dataIn as ProductProjection;
67
75
  const base = this.newModel();
68
76
 
77
+
69
78
  base.identifier = { key: data.id };
70
79
  base.name = data.name[session.languageContext.locale];
71
80
  base.slug = data.slug[session.languageContext.locale];
@@ -1,35 +1,49 @@
1
- import {
2
- SearchProvider,
3
- SearchQueryByTerm,
1
+ import { SearchProvider } from '@reactionary/core';
2
+ import type {
4
3
  SearchResult,
5
4
  SearchResultProduct,
6
- Session,
7
5
  Cache,
6
+ SearchQueryByTerm,
7
+ Session,
8
8
  } from '@reactionary/core';
9
+
9
10
  import { CommercetoolsClient } from '../core/client';
10
11
  import z from 'zod';
11
12
  import { CommercetoolsConfiguration } from '../schema/configuration.schema';
13
+ import { traced } from '@reactionary/otel';
12
14
 
13
15
  export class CommercetoolsSearchProvider<
14
16
  T extends SearchResult = SearchResult
15
17
  > extends SearchProvider<T> {
16
18
  protected config: CommercetoolsConfiguration;
17
19
 
18
- constructor(config: CommercetoolsConfiguration, schema: z.ZodType<T>, cache: Cache) {
20
+ constructor(
21
+ config: CommercetoolsConfiguration,
22
+ schema: z.ZodType<T>,
23
+ cache: Cache
24
+ ) {
19
25
  super(schema, cache);
20
26
 
21
27
  this.config = config;
22
28
  }
23
29
 
30
+ @traced()
31
+ protected getClient(session: Session) {
32
+ const token = session.identity.keyring.find(x => x.service === 'commercetools')?.token;
33
+ const client = new CommercetoolsClient(this.config).getClient(
34
+ token
35
+ );
36
+ return client.withProjectKey({ projectKey: this.config.projectKey }).productProjections();
37
+ }
38
+
39
+
24
40
  public override async queryByTerm(
25
41
  payload: SearchQueryByTerm,
26
42
  session: Session
27
43
  ): Promise<T> {
28
- const client = new CommercetoolsClient(this.config).createAnonymousClient();
44
+ const client = this.getClient(session);
29
45
 
30
46
  const remote = await client
31
- .withProjectKey({ projectKey: this.config.projectKey })
32
- .productProjections()
33
47
  .search()
34
48
  .get({
35
49
  queryArgs: {
@@ -43,9 +57,23 @@ export class CommercetoolsSearchProvider<
43
57
  return this.parseSearchResult(remote, payload, session);
44
58
  }
45
59
 
46
- protected parseSearchResult(remote: unknown, payload: SearchQueryByTerm, session: Session): T {
60
+ protected parseSearchResult(
61
+ remote: unknown,
62
+ payload: SearchQueryByTerm,
63
+ session: Session
64
+ ): T {
47
65
  const result = this.newModel();
48
- const remoteData = remote as { body: { results: Array<{ id: string; name: Record<string, string>; slug?: Record<string, string>; masterVariant: { images?: Array<{ url?: string }> } }>; total?: number } };
66
+ const remoteData = remote as {
67
+ body: {
68
+ results: Array<{
69
+ id: string;
70
+ name: Record<string, string>;
71
+ slug?: Record<string, string>;
72
+ masterVariant: { images?: Array<{ url?: string }> };
73
+ }>;
74
+ total?: number;
75
+ };
76
+ };
49
77
 
50
78
  result.identifier = payload.search;
51
79
 
@@ -54,20 +82,20 @@ export class CommercetoolsSearchProvider<
54
82
  identifier: { key: p.id },
55
83
  name: p.name[session.languageContext.locale] || p.id,
56
84
  slug: p.slug?.[session.languageContext.locale] || p.id,
57
- image: p.masterVariant.images?.[0]?.url || 'https://placehold.co/400'
85
+ image: p.masterVariant.images?.[0]?.url || 'https://placehold.co/400',
58
86
  };
59
87
 
60
88
  result.products.push(product);
61
89
  }
62
90
 
63
- result.pages = Math.ceil((remoteData.body.total || 0) / payload.search.pageSize);
91
+ result.pages = Math.ceil(
92
+ (remoteData.body.total || 0) / payload.search.pageSize
93
+ );
64
94
  result.meta = {
65
95
  cache: { hit: false, key: payload.search.term },
66
- placeholder: false
96
+ placeholder: false,
67
97
  };
68
98
 
69
99
  return this.assert(result);
70
100
  }
71
-
72
-
73
101
  }
@@ -6,6 +6,7 @@ export const CommercetoolsCapabilitiesSchema = CapabilitiesSchema.pick({
6
6
  search: true,
7
7
  identity: true,
8
8
  cart: true,
9
+ cartPayment: true,
9
10
  inventory: true,
10
11
  price: true,
11
12
  category: true,
@@ -0,0 +1,18 @@
1
+ import { CartIdentifierSchema, OrderIdentifierSchema, PaymentInstructionIdentifierSchema } from "@reactionary/core";
2
+ import z from "zod";
3
+
4
+ export const CommercetoolsCartIdentifierSchema = CartIdentifierSchema.extend({
5
+ version: z.number().default(0)
6
+ });
7
+
8
+ export const CommercetoolsCartPaymentInstructionIdentifierSchema = PaymentInstructionIdentifierSchema.extend({
9
+ version: z.number().default(0),
10
+ });
11
+
12
+ export const CommercetoolsOrderIdentifierSchema = OrderIdentifierSchema.extend({
13
+ version: z.number().default(0)
14
+ });
15
+
16
+ export type CommercetoolsCartIdentifier = z.infer<typeof CommercetoolsCartIdentifierSchema>;
17
+ export type CommercetoolsCartPaymentInstructionIdentifier = z.infer<typeof CommercetoolsCartPaymentInstructionIdentifierSchema>;
18
+ export type CommercetoolsOrderIdentifier = z.infer<typeof CommercetoolsOrderIdentifierSchema>;
@@ -5,7 +5,8 @@ export const CommercetoolsConfigurationSchema = z.looseObject({
5
5
  authUrl: z.string(),
6
6
  apiUrl: z.string(),
7
7
  clientId: z.string(),
8
- clientSecret: z.string()
8
+ clientSecret: z.string(),
9
+ scopes: z.array(z.string()).default(() => []),
9
10
  });
10
11
 
11
12
  export type CommercetoolsConfiguration = z.infer<typeof CommercetoolsConfigurationSchema>;
@@ -0,0 +1,149 @@
1
+ import 'dotenv/config';
2
+ import {
3
+ Cart,
4
+ CartMutationAddPaymentMethodSchema,
5
+ CartPaymentInstructionSchema,
6
+ CartSchema,
7
+ IdentitySchema,
8
+ NoOpCache,
9
+ Session,
10
+ } from '@reactionary/core';
11
+ import {
12
+ createAnonymousTestSession,
13
+ getCommercetoolsTestConfiguration,
14
+ } from './test-utils';
15
+ import { CommercetoolsCartProvider } from '../providers/cart.provider';
16
+ import { CommercetoolsIdentityProvider } from '../providers/identity.provider';
17
+ import { CommercetoolsCartPaymentProvider } from '../providers/cart-payment.provider';
18
+ import { CartPaymentMutationAddPayment } from 'core/src/schemas/mutations/cart-payment.mutation';
19
+
20
+ const testData = {
21
+ skuWithoutTiers: 'SGB-01',
22
+ skuWithTiers: 'GMCT-01',
23
+ };
24
+
25
+ describe('Commercetools Cart Payment Provider', () => {
26
+ let provider: CommercetoolsCartPaymentProvider;
27
+ let cartProvider: CommercetoolsCartProvider;
28
+ let identityProvider: CommercetoolsIdentityProvider;
29
+ let session: Session;
30
+
31
+ beforeAll(() => {
32
+ provider = new CommercetoolsCartPaymentProvider(
33
+ getCommercetoolsTestConfiguration(),
34
+ CartPaymentInstructionSchema,
35
+ new NoOpCache()
36
+ );
37
+ cartProvider = new CommercetoolsCartProvider(
38
+ getCommercetoolsTestConfiguration(),
39
+ CartSchema,
40
+ new NoOpCache()
41
+ );
42
+ identityProvider = new CommercetoolsIdentityProvider(
43
+ getCommercetoolsTestConfiguration(),
44
+ IdentitySchema,
45
+ new NoOpCache()
46
+ );
47
+ });
48
+
49
+ beforeEach(() => {
50
+ session = createAnonymousTestSession();
51
+ });
52
+
53
+ describe('anonymous sessions', () => {
54
+ let cart: Cart;
55
+
56
+ beforeEach(async () => {
57
+ cart = await cartProvider.add(
58
+ {
59
+ cart: { key: '', version: 0 },
60
+ sku: {
61
+ key: testData.skuWithoutTiers,
62
+ },
63
+ quantity: 1,
64
+ },
65
+ session
66
+ );
67
+ });
68
+
69
+ it('a new cart will return 0 payment instructions', async () => {
70
+ const payments = await provider.getByCartIdentifier(
71
+ { cart: cart.identifier, status: undefined },
72
+ session
73
+ );
74
+ expect(payments.length).toBe(0);
75
+ });
76
+
77
+ it('can initiate a new payment', async () => {
78
+ const payment = await provider.initiatePaymentForCart(
79
+ {
80
+ cart: cart.identifier,
81
+ paymentInstruction: {
82
+ paymentMethod: {
83
+ method: 'stripe',
84
+ name: 'Stripe',
85
+ paymentProcessor: 'stripe',
86
+ },
87
+ amount: {
88
+ value: cart.price.grandTotal.value,
89
+ currency: cart.price.grandTotal.currency,
90
+ },
91
+ protocolData: [{ key: 'test-key', value: 'test-value' }],
92
+ },
93
+ },
94
+ session
95
+ );
96
+
97
+ expect(payment.identifier.key).toBeDefined();
98
+
99
+ // verify that we can fetch it again.
100
+ const payments = await provider.getByCartIdentifier(
101
+ { cart: cart.identifier, status: undefined },
102
+ session
103
+ );
104
+ expect(payments.length).toBe(1);
105
+ expect(payments[0].identifier.key).toBe(payment.identifier.key);
106
+ });
107
+
108
+
109
+ it('can cancel an in-progress payment', async () => {
110
+ const payment = await provider.initiatePaymentForCart(
111
+ {
112
+ cart: cart.identifier,
113
+ paymentInstruction: {
114
+ paymentMethod: {
115
+ method: 'stripe',
116
+ name: 'Stripe',
117
+ paymentProcessor: 'stripe',
118
+ },
119
+ amount: {
120
+ value: cart.price.grandTotal.value,
121
+ currency: cart.price.grandTotal.currency,
122
+ },
123
+ protocolData: [{ key: 'test-key', value: 'test-value' }],
124
+ },
125
+ },
126
+ session
127
+ );
128
+ expect(payment.identifier.key).toBeDefined();
129
+
130
+ const cancelledPayment = await provider.cancelPaymentInstruction(
131
+ {
132
+ cart: cart.identifier,
133
+ paymentInstruction: payment.identifier
134
+ },
135
+ session
136
+ );
137
+ expect(cancelledPayment.status).toBe('canceled');
138
+
139
+
140
+ // verify that it is gone
141
+ const payments = await provider.getByCartIdentifier(
142
+ { cart: cart.identifier, status: undefined },
143
+ session
144
+ );
145
+ expect(payments.length).toBe(0);
146
+ expect(payments[0].identifier.key).toBe(payment.identifier.key);
147
+ });
148
+ });
149
+ });
@@ -3,6 +3,13 @@ import { CartSchema, CategorySchema, IdentitySchema, NoOpCache, ProductSchema, S
3
3
  import { createAnonymousTestSession, getCommercetoolsTestConfiguration } from './test-utils';
4
4
  import { CommercetoolsCartProvider } from '../providers/cart.provider';
5
5
  import { CommercetoolsIdentityProvider } from '../providers/identity.provider';
6
+
7
+
8
+ const testData = {
9
+ skuWithoutTiers: 'SGB-01',
10
+ skuWithTiers: 'GMCT-01'
11
+ }
12
+
6
13
  describe('Commercetools Cart Provider', () => {
7
14
  let provider: CommercetoolsCartProvider;
8
15
  let identityProvider: CommercetoolsIdentityProvider;
@@ -32,15 +39,15 @@ describe('Commercetools Cart Provider', () => {
32
39
  it('should be able to add an item to a cart', async () => {
33
40
  const cart = await provider.add({
34
41
  cart: { key: '' },
35
- product: {
36
- key: '4d28f98d-c446-446e-b59a-d9f718e5b98a',
42
+ sku: {
43
+ key: testData.skuWithoutTiers,
37
44
  },
38
45
  quantity: 1
39
46
  }, session);
40
47
 
41
48
  expect(cart.identifier.key).toBeDefined();
42
49
  expect(cart.items.length).toBe(1);
43
- expect(cart.items[0].product.key).toBe('4d28f98d-c446-446e-b59a-d9f718e5b98a');
50
+ expect(cart.items[0].sku.key).toBe(testData.skuWithoutTiers);
44
51
  expect(cart.items[0].quantity).toBe(1);
45
52
 
46
53
  expect(cart.items[0].price.totalPrice.value).toBeGreaterThan(0);
@@ -56,13 +63,38 @@ describe('Commercetools Cart Provider', () => {
56
63
 
57
64
  });
58
65
 
66
+ it('can add multiple different items to a cart', async () => {
67
+
68
+ const cart = await provider.add({
69
+ cart: { key: '' },
70
+ sku: {
71
+ key: testData.skuWithoutTiers,
72
+ },
73
+ quantity: 1
74
+ }, session);
75
+
76
+
77
+ const updatedCart = await provider.add({
78
+ cart: cart.identifier,
79
+ sku: {
80
+ key: testData.skuWithTiers,
81
+ },
82
+ quantity: 2
83
+ }, session);
84
+
85
+ expect(updatedCart.items.length).toBe(2);
86
+ expect(updatedCart.items[0].sku.key).toBe(testData.skuWithoutTiers);
87
+ expect(updatedCart.items[0].quantity).toBe(1);
88
+ expect(updatedCart.items[1].sku.key).toBe(testData.skuWithTiers);
89
+ expect(updatedCart.items[1].quantity).toBe(2);
90
+ });
59
91
 
60
92
  it('should be able to change quantity of an item in a cart', async () => {
61
93
 
62
94
  const cart = await provider.add({
63
95
  cart: { key: '' },
64
- product: {
65
- key: '4d28f98d-c446-446e-b59a-d9f718e5b98a',
96
+ sku: {
97
+ key: testData.skuWithoutTiers,
66
98
  },
67
99
  quantity: 1
68
100
  }, session);
@@ -73,9 +105,9 @@ describe('Commercetools Cart Provider', () => {
73
105
  quantity: 3
74
106
  }, session);
75
107
 
76
- expect(updatedCart.identifier.key).toBe(cart.identifier.key);
108
+
77
109
  expect(updatedCart.items.length).toBe(1);
78
- expect(updatedCart.items[0].product.key).toBe('4d28f98d-c446-446e-b59a-d9f718e5b98a');
110
+ expect(updatedCart.items[0].sku.key).toBe(testData.skuWithoutTiers);
79
111
  expect(updatedCart.items[0].quantity).toBe(3);
80
112
 
81
113
  expect(updatedCart.items[0].price.totalPrice.value).toBe(cart.items[0].price.totalPrice.value * 3);
@@ -88,8 +120,8 @@ describe('Commercetools Cart Provider', () => {
88
120
 
89
121
  const cart = await provider.add({
90
122
  cart: { key: '' },
91
- product: {
92
- key: '4d28f98d-c446-446e-b59a-d9f718e5b98a',
123
+ sku: {
124
+ key: testData.skuWithoutTiers,
93
125
  },
94
126
  quantity: 1
95
127
  }, session);
@@ -98,10 +130,37 @@ describe('Commercetools Cart Provider', () => {
98
130
  cart: cart.identifier,
99
131
  item: cart.items[0].identifier,
100
132
  }, session);
101
- expect(updatedCart.identifier.key).toBe(cart.identifier.key);
133
+
102
134
  expect(updatedCart.items.length).toBe(0);
103
135
  });
104
136
 
137
+ it('should be able to delete a cart', async () => {
138
+
139
+ const cart = await provider.add({
140
+ cart: { key: '' },
141
+ sku: {
142
+ key: testData.skuWithoutTiers,
143
+ },
144
+ quantity: 1
145
+ }, session);
146
+
147
+ expect(cart.items.length).toBe(1);
148
+ expect(cart.identifier.key).toBeTruthy();
149
+
150
+ const deletedCart = await provider.deleteCart({
151
+ cart: cart.identifier,
152
+ }, session);
153
+
154
+ expect(deletedCart.items.length).toBe(0);
155
+ expect(deletedCart.identifier.key).toBe('');
156
+
157
+ const originalCart = await provider.getById({
158
+ cart: cart.identifier,
159
+ }, session);
160
+
161
+ expect(originalCart.items.length).toBe(0);
162
+ });
163
+
105
164
  /**
106
165
  it('should be able to create a cart for an anonymous user, then login and merge the cart', async () => {
107
166
  });
@@ -1,11 +1,7 @@
1
1
  import 'dotenv/config'
2
-
3
- import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';
4
-
5
- import { CategorySchema, NoOpCache, ProductSchema, Session } from '@reactionary/core';
2
+ import { CategorySchema, NoOpCache, Session } from '@reactionary/core';
6
3
  import { CommercetoolsCategoryProvider } from '../providers/category.provider';
7
4
  import { createAnonymousTestSession, getCommercetoolsTestConfiguration } from './test-utils';
8
- import { getTracer, shutdownOtel } from '@reactionary/otel';
9
5
 
10
6
  const testData = {
11
7
  topCategories: [
@@ -168,13 +164,4 @@ describe('Commercetools Category Provider', () => {
168
164
  expect(result.meta.placeholder).toBe(true);
169
165
 
170
166
  });
171
-
172
- it('traces execution of getById', async () => {
173
- const tracer = getTracer();
174
- const span = tracer.startSpan('test-span');
175
- const result = await provider.getById({ id: { key: 'home-decor'}}, session);
176
- span.end();
177
- await shutdownOtel();
178
- });
179
-
180
167
  });
@@ -28,8 +28,35 @@ describe('Commercetools Product Provider', () => {
28
28
  it('should be able to get a product by id', async () => {
29
29
  const result = await provider.getById( { id: testData.product.id }, session);
30
30
 
31
+ expect(result).toBeTruthy();
31
32
  expect(result.identifier.key).toBe(testData.product.id);
33
+ expect(result.meta.placeholder).toBe(false);
32
34
  expect(result.name).toBe(testData.product.name);
33
35
  expect(result.image).toBe(testData.product.image);
34
36
  });
37
+
38
+ it('should be able to get a product by slug', async () => {
39
+ const result = await provider.getBySlug( { slug: 'sunnai-glass-bowl' }, session);
40
+
41
+ expect(result).toBeTruthy();
42
+ if (result) {
43
+ expect(result.meta.placeholder).toBe(false);
44
+ expect(result.identifier.key).toBe(testData.product.id);
45
+ expect(result.name).toBe(testData.product.name);
46
+ expect(result.image).toBe(testData.product.image);
47
+ }
48
+ });
49
+
50
+ it('should return null for unknown slug', async () => {
51
+ const result = await provider.getBySlug( { slug: 'unknown-slug' }, session);
52
+
53
+ expect(result).toBeNull();
54
+ });
55
+
56
+ it('should return a placeholder product for unknown id', async () => {
57
+ const result = await provider.getById( { id: 'unknown-id' }, session);
58
+
59
+ expect(result).toBeTruthy();
60
+ expect(result.meta.placeholder).toBe(true);
61
+ });
35
62
  });