@labdigital/commercetools-mock 2.16.1 → 2.17.1

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,7 +1,7 @@
1
1
  {
2
2
  "name": "@labdigital/commercetools-mock",
3
3
  "author": "Michael van Tellingen",
4
- "version": "2.16.1",
4
+ "version": "2.17.1",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.cjs",
7
7
  "module": "dist/index.js",
@@ -2,15 +2,26 @@ import { describe, it, expect, beforeEach } from 'vitest'
2
2
  import express from 'express'
3
3
  import supertest from 'supertest'
4
4
  import { OAuth2Server } from './server'
5
+ import { CustomerRepository } from '../repositories/customer'
6
+ import { AbstractStorage, InMemoryStorage } from '../storage'
7
+ import { getBaseResourceProperties } from '../helpers'
8
+ import { hashPassword } from '../lib/password'
5
9
 
6
10
  describe('OAuth2Server', () => {
7
11
  let app: express.Express
8
12
  let server: OAuth2Server
9
13
 
14
+ let storage: AbstractStorage
15
+ let customerRepository: CustomerRepository
16
+
10
17
  beforeEach(() => {
11
18
  server = new OAuth2Server({ enabled: true, validate: false })
12
19
  app = express()
13
20
  app.use(server.createRouter())
21
+
22
+ storage = new InMemoryStorage()
23
+ customerRepository = new CustomerRepository(storage)
24
+ server.setCustomerRepository(customerRepository)
14
25
  })
15
26
 
16
27
  describe('POST /token', () => {
@@ -74,16 +85,48 @@ describe('OAuth2Server', () => {
74
85
 
75
86
  expect(response.status).toBe(200)
76
87
  expect(response.body).toHaveProperty('access_token')
88
+ expect(response.body).toEqual({
89
+ scope: expect.stringMatching(/anonymous_id:([^\s]+)/),
90
+ access_token: expect.stringMatching(/\S{8,}==$/),
91
+ refresh_token: expect.stringMatching(/test-project:\S{8,}==$/),
92
+ expires_in: 172800,
93
+ token_type: 'Bearer',
94
+ })
95
+ })
96
+ })
97
+
98
+ describe('POST /:projectKey/customers/token', () => {
99
+ it('should return a token for customer access', async () => {
100
+ const projectKey = 'test-project'
77
101
 
78
- const matches = response.body.scope?.match(
79
- /(customer_id|anonymous_id):([^\s]+)/
80
- )
81
- if (matches) {
82
- expect(matches[1]).toBe('anonymous_id')
83
- expect(matches[2]).toBeDefined()
84
- } else {
85
- expect(response.body.scope).toBe('')
86
- }
102
+ storage.add(projectKey, 'customer', {
103
+ ...getBaseResourceProperties(),
104
+ email: 'j.doe@example.org',
105
+ password: hashPassword('password'),
106
+ addresses: [],
107
+ authenticationMode: 'password',
108
+ isEmailVerified: true,
109
+ })
110
+
111
+ const response = await supertest(app)
112
+ .post(`/${projectKey}/customers/token`)
113
+ .auth('validClientId', 'validClientSecret')
114
+ .query({
115
+ grant_type: 'password',
116
+ username: 'j.doe@example.org',
117
+ password: 'password',
118
+ scope: `${projectKey}:manage_my_profile`,
119
+ })
120
+ .send()
121
+
122
+ expect(response.status).toBe(200)
123
+ expect(response.body).toEqual({
124
+ scope: expect.stringMatching(/customer_id:([^\s]+)/),
125
+ access_token: expect.stringMatching(/\S{8,}==$/),
126
+ refresh_token: expect.stringMatching(/test-project:\S{8,}==$/),
127
+ expires_in: 172800,
128
+ token_type: 'Bearer',
129
+ })
87
130
  })
88
131
  })
89
132
  })
@@ -221,6 +221,7 @@ export class OAuth2Server {
221
221
  response: Response,
222
222
  next: NextFunction
223
223
  ) {
224
+ const projectKey = request.params.projectKey
224
225
  const grantType = request.query.grant_type || request.body.grant_type
225
226
  if (!grantType) {
226
227
  return next(
@@ -262,7 +263,7 @@ export class OAuth2Server {
262
263
  }
263
264
 
264
265
  const customer = result.results[0]
265
- const token = this.store.getCustomerToken(scope, customer.id)
266
+ const token = this.store.getCustomerToken(projectKey, customer.id, scope)
266
267
  return response.status(200).send(token)
267
268
  }
268
269
  }
@@ -288,6 +289,7 @@ export class OAuth2Server {
288
289
  response: Response,
289
290
  next: NextFunction
290
291
  ) {
292
+ const projectKey = request.params.projectKey
291
293
  const grantType = request.query.grant_type || request.body.grant_type
292
294
  if (!grantType) {
293
295
  return next(
@@ -307,7 +309,11 @@ export class OAuth2Server {
307
309
 
308
310
  const anonymous_id = undefined
309
311
 
310
- const token = this.store.getAnonymousToken(scope, anonymous_id)
312
+ const token = this.store.getAnonymousToken(
313
+ projectKey,
314
+ anonymous_id,
315
+ scope
316
+ )
311
317
  return response.status(200).send(token)
312
318
  }
313
319
  }
@@ -33,7 +33,11 @@ export class OAuth2Store {
33
33
  return token
34
34
  }
35
35
 
36
- getAnonymousToken(scope: string, anonymousId: string | undefined) {
36
+ getAnonymousToken(
37
+ projectKey: string,
38
+ anonymousId: string | undefined,
39
+ scope: string
40
+ ) {
37
41
  if (!anonymousId) {
38
42
  anonymousId = uuidv4()
39
43
  }
@@ -44,13 +48,13 @@ export class OAuth2Store {
44
48
  scope: scope
45
49
  ? `${scope} anonymous_id:${anonymousId}`
46
50
  : `anonymous_id:${anonymousId}`,
47
- refresh_token: `my-project-${randomBytes(16).toString('base64')}`,
51
+ refresh_token: `${projectKey}:${randomBytes(16).toString('base64')}`,
48
52
  }
49
53
  this.addToken(token)
50
54
  return token
51
55
  }
52
56
 
53
- getCustomerToken(scope: string, customerId: string) {
57
+ getCustomerToken(projectKey: string, customerId: string, scope: string) {
54
58
  const token: Token = {
55
59
  access_token: randomBytes(16).toString('base64'),
56
60
  token_type: 'Bearer',
@@ -58,7 +62,7 @@ export class OAuth2Store {
58
62
  scope: scope
59
63
  ? `${scope} customer_id:${customerId}`
60
64
  : `customer_id:${customerId}`,
61
- refresh_token: `my-project-${randomBytes(16).toString('base64')}`,
65
+ refresh_token: `${projectKey}:${randomBytes(16).toString('base64')}`,
62
66
  }
63
67
  this.addToken(token)
64
68
  return token
@@ -27,6 +27,7 @@ import {
27
27
  type CartRemoveDiscountCodeAction,
28
28
  type ProductVariant,
29
29
  type CartSetCustomShippingMethodAction,
30
+ type CartSetDirectDiscountsAction,
30
31
  } from '@commercetools/platform-sdk'
31
32
  import { v4 as uuidv4 } from 'uuid'
32
33
  import { CommercetoolsError } from '../exceptions.js'
@@ -455,6 +456,17 @@ export class CartRepository extends AbstractResourceRepository<'cart'> {
455
456
  }
456
457
  }
457
458
  },
459
+ setDirectDiscounts: (
460
+ context: RepositoryContext,
461
+ resource: Writable<Cart>,
462
+ { discounts }: CartSetDirectDiscountsAction
463
+ ) => {
464
+ // Doesn't apply any discounts logic, just sets the directDiscounts field
465
+ resource.directDiscounts = discounts.map((discount) => ({
466
+ ...discount,
467
+ id: uuidv4(),
468
+ }))
469
+ },
458
470
  setLocale: (
459
471
  context: RepositoryContext,
460
472
  resource: Writable<Cart>,
@@ -440,6 +440,56 @@ describe('Cart Update Actions', () => {
440
440
  expect(response.body.country).toBe('NL')
441
441
  })
442
442
 
443
+ test('setDirectDiscounts', async () => {
444
+ assert(cart, 'cart not created')
445
+
446
+ const response = await supertest(ctMock.app)
447
+ .post(`/dummy/carts/${cart.id}`)
448
+ .send({
449
+ version: 1,
450
+ actions: [
451
+ {
452
+ action: 'setDirectDiscounts',
453
+ discounts: [
454
+ {
455
+ target: { type: 'totalPrice' },
456
+ value: {
457
+ money: [
458
+ {
459
+ centAmount: 500,
460
+ currencyCode: 'EUR',
461
+ fractionDigits: 2,
462
+ type: 'centPrecision',
463
+ },
464
+ ],
465
+ type: 'absolute',
466
+ },
467
+ },
468
+ ],
469
+ },
470
+ ],
471
+ })
472
+ expect(response.status).toBe(200)
473
+ expect(response.body.version).toBe(2)
474
+ expect(response.body.directDiscounts).toMatchObject([
475
+ {
476
+ id: expect.any(String),
477
+ target: { type: 'totalPrice' },
478
+ value: {
479
+ money: [
480
+ {
481
+ centAmount: 500,
482
+ currencyCode: 'EUR',
483
+ fractionDigits: 2,
484
+ type: 'centPrecision',
485
+ },
486
+ ],
487
+ type: 'absolute',
488
+ },
489
+ },
490
+ ])
491
+ })
492
+
443
493
  test('setCustomerEmail', async () => {
444
494
  assert(cart, 'cart not created')
445
495
 
@@ -1,9 +1,10 @@
1
- import { Router } from 'express'
1
+ import { type Request, type Response, Router } from 'express'
2
2
  import { v4 as uuidv4 } from 'uuid'
3
3
  import { getBaseResourceProperties } from '../helpers.js'
4
4
  import { CustomerRepository } from '../repositories/customer.js'
5
5
  import { getRepositoryContext } from '../repositories/helpers.js'
6
6
  import AbstractService from './abstract.js'
7
+ import { CustomerSignInResult } from '@commercetools/platform-sdk'
7
8
 
8
9
  export class CustomerService extends AbstractService {
9
10
  public repository: CustomerRepository
@@ -17,6 +18,20 @@ export class CustomerService extends AbstractService {
17
18
  return 'customers'
18
19
  }
19
20
 
21
+ post(request: Request, response: Response) {
22
+ const draft = request.body
23
+ const resource = this.repository.create(
24
+ getRepositoryContext(request),
25
+ draft
26
+ )
27
+ const expanded = this._expandWithId(request, resource.id)
28
+
29
+ const result: CustomerSignInResult = {
30
+ customer: expanded,
31
+ }
32
+ return response.status(this.createStatusCode).send(result)
33
+ }
34
+
20
35
  extraRoutes(parent: Router) {
21
36
  parent.post('/password-token', (request, response) => {
22
37
  const customer = this.repository.query(getRepositoryContext(request), {