@defra-fish/dynamics-lib 1.73.0-rc.8 → 1.73.0-rc.9

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": "@defra-fish/dynamics-lib",
3
- "version": "1.73.0-rc.8",
3
+ "version": "1.73.0-rc.9",
4
4
  "description": "Framework to support integration with dynamics",
5
5
  "type": "module",
6
6
  "engines": {
@@ -36,12 +36,12 @@
36
36
  "cache-manager": "3.6.0",
37
37
  "cache-manager-ioredis": "2.1.0",
38
38
  "debug": "4.3.3",
39
- "dynamics-web-api": "1.7.3",
39
+ "dynamics-web-api": "2.4.0",
40
40
  "joi": "17.13.3",
41
41
  "moment": "2.29.1",
42
42
  "pluralize": "8.0.0",
43
43
  "simple-oauth2": "4.3.0",
44
44
  "uuid": "8.3.2"
45
45
  },
46
- "gitHead": "f74c85a07f6d13c63b8c2a6117722532406a2e49"
46
+ "gitHead": "50eea14059f2624d3cb0a969607efc0489645a69"
47
47
  }
@@ -3,7 +3,7 @@ const { readFileSync } = jest.requireActual('fs')
3
3
  const Path = jest.requireActual('path')
4
4
  const optionSetDataPath = Path.join(Project.root, 'src', '__mocks__', 'option-set-data.json')
5
5
 
6
- export const configureDynamicsWebApiMock = (DynamicsWebApi = jest.genMockFromModule('dynamics-web-api')) => {
6
+ export const configureDynamicsWebApiMock = DynamicsWebApi => {
7
7
  let expectedResponse = {}
8
8
  let nextResponses = {}
9
9
  let callError = {}
@@ -53,12 +53,12 @@ export const configureDynamicsWebApiMock = (DynamicsWebApi = jest.genMockFromMod
53
53
  }
54
54
  }
55
55
 
56
- DynamicsWebApi.prototype.retrieveRequest = jest.fn(async () => responseCapableMethod('retrieveRequest'))
57
- DynamicsWebApi.prototype.createRequest = jest.fn(async () => responseCapableMethod('createRequest'))
58
- DynamicsWebApi.prototype.updateRequest = jest.fn(async () => responseCapableMethod('updateRequest'))
59
- DynamicsWebApi.prototype.retrieveMultipleRequest = jest.fn(async () => responseCapableMethod('retrieveMultipleRequest'))
56
+ DynamicsWebApi.prototype.retrieve = jest.fn(async () => responseCapableMethod('retrieve'))
57
+ DynamicsWebApi.prototype.create = jest.fn(async () => responseCapableMethod('create'))
58
+ DynamicsWebApi.prototype.update = jest.fn(async () => responseCapableMethod('update'))
59
+ DynamicsWebApi.prototype.retrieveMultiple = jest.fn(async () => responseCapableMethod('retrieveMultiple'))
60
60
  DynamicsWebApi.prototype.retrieveGlobalOptionSets = jest.fn(async () => responseCapableMethod('retrieveGlobalOptionSets'))
61
- DynamicsWebApi.prototype.executeUnboundFunction = jest.fn(async functionName => {
61
+ DynamicsWebApi.prototype.callFunction = jest.fn(async functionName => {
62
62
  let returnValue = null
63
63
  if (functionName === 'RetrieveVersion') {
64
64
  returnValue = {
@@ -1,3 +1,5 @@
1
1
  import { configureDynamicsWebApiMock } from './dynamics-web-api-mock-helper.js'
2
- const DynamicsWebApi = jest.genMockFromModule('dynamics-web-api')
3
- export default configureDynamicsWebApiMock(DynamicsWebApi)
2
+ const DynamicsWebApi = jest.fn()
3
+ DynamicsWebApi.prototype = {}
4
+ configureDynamicsWebApiMock(DynamicsWebApi)
5
+ export { DynamicsWebApi }
@@ -1,39 +1,145 @@
1
1
  import { config } from '../dynamics-client.js'
2
2
  import SimpleOAuth2 from 'simple-oauth2'
3
3
 
4
+ const MOCK_SCOPE = 'https://resource/.default'
5
+ const PREEMPTIVE_TOKEN_EXPIRY_SECONDS = 60
6
+
7
+ const setRequiredEnv = () => {
8
+ process.env.DYNAMICS_API_PATH = 'https://test-server'
9
+ process.env.DYNAMICS_API_VERSION = '9.1'
10
+ process.env.DYNAMICS_API_TIMEOUT = 60000
11
+ process.env.OAUTH_AUTHORITY_HOST_URL = 'https://test-authority/'
12
+ process.env.OAUTH_TENANT = 'tenant'
13
+ process.env.OAUTH_CLIENT_ID = 'clientId'
14
+ process.env.OAUTH_CLIENT_SECRET = 'clientSecret'
15
+ process.env.OAUTH_SCOPE = MOCK_SCOPE
16
+ }
17
+
18
+ const createConfiguredClient = () => {
19
+ const dynamicsApiConfig = config()
20
+ const oauthClient = SimpleOAuth2.ClientCredentials.mock.results.at(-1).value
21
+ return { dynamicsApiConfig, oauthClient }
22
+ }
23
+
24
+ const seedCachedToken = async (dynamicsApiConfig, token = 'MOCK TOKEN') => {
25
+ SimpleOAuth2.__setMockTokenReturnValue(token)
26
+ await dynamicsApiConfig.onTokenRefresh()
27
+ }
28
+
4
29
  describe('dynamics-client', () => {
5
- it('is configured via environment variables', async () => {
6
- process.env.DYNAMICS_API_PATH = 'https://test-server/api/data/v9.1/'
7
- process.env.DYNAMICS_API_VERSION = '9.1'
8
- process.env.DYNAMICS_API_TIMEOUT = 60000
9
- process.env.OAUTH_AUTHORITY_HOST_URL = 'https://test-authority/'
10
- process.env.OAUTH_TENANT = 'tenant'
11
- process.env.OAUTH_CLIENT_ID = 'clientId'
12
- process.env.OAUTH_CLIENT_SECRET = 'clientSecret'
13
- process.env.OAUTH_RESOURCE = 'https://resource/.default'
14
- const dynamicsApiConfig = config()
15
-
16
- expect(dynamicsApiConfig).toMatchObject({
17
- webApiUrl: process.env.DYNAMICS_API_PATH,
18
- webApiVersion: process.env.DYNAMICS_API_VERSION,
19
- timeout: `${process.env.DYNAMICS_API_TIMEOUT}`,
20
- onTokenRefresh: expect.any(Function)
21
- })
22
- const testCallback = jest.fn()
23
- await dynamicsApiConfig.onTokenRefresh(testCallback)
24
- expect(testCallback).toHaveBeenCalledWith('MOCK TOKEN')
25
- })
26
-
27
- it('caches tokens until they expire', async () => {
28
- const dynamicsApiConfig = config()
29
- const testCallback = jest.fn()
30
- await dynamicsApiConfig.onTokenRefresh(testCallback)
31
- expect(testCallback).toHaveBeenLastCalledWith('MOCK TOKEN')
32
- SimpleOAuth2.__setMockTokenReturnValue('NEW MOCK TOKEN')
33
- await dynamicsApiConfig.onTokenRefresh(testCallback)
34
- expect(testCallback).toHaveBeenLastCalledWith('MOCK TOKEN')
30
+ beforeEach(() => {
31
+ jest.clearAllMocks()
32
+ SimpleOAuth2.__setMockTokenReturnValue('MOCK TOKEN')
33
+ SimpleOAuth2.__setMockTokenExpired(false)
34
+ setRequiredEnv()
35
+ })
36
+
37
+ it('exposes serverUrl from DYNAMICS_API_PATH', () => {
38
+ const { dynamicsApiConfig } = createConfiguredClient()
39
+
40
+ expect(dynamicsApiConfig.serverUrl).toBe(process.env.DYNAMICS_API_PATH)
41
+ })
42
+
43
+ it('exposes dataApi.version from DYNAMICS_API_VERSION', () => {
44
+ const { dynamicsApiConfig } = createConfiguredClient()
45
+
46
+ expect(dynamicsApiConfig.dataApi.version).toBe(process.env.DYNAMICS_API_VERSION)
47
+ })
48
+
49
+ it('exposes timeout from DYNAMICS_API_TIMEOUT', () => {
50
+ const { dynamicsApiConfig } = createConfiguredClient()
51
+
52
+ expect(dynamicsApiConfig.timeout).toBe(`${process.env.DYNAMICS_API_TIMEOUT}`)
53
+ })
54
+
55
+ it('exposes onTokenRefresh as a function', () => {
56
+ const { dynamicsApiConfig } = createConfiguredClient()
57
+
58
+ expect(typeof dynamicsApiConfig.onTokenRefresh).toBe('function')
59
+ })
60
+
61
+ it('returns getToken access token when access token cache is empty', async () => {
62
+ SimpleOAuth2.__setMockTokenReturnValue('FIRST TOKEN')
63
+ const { dynamicsApiConfig } = createConfiguredClient()
64
+
65
+ const token = await dynamicsApiConfig.onTokenRefresh()
66
+
67
+ expect(token).toBe('FIRST TOKEN')
68
+ })
69
+
70
+ it('calls getToken once when access token cache is empty', async () => {
71
+ const { dynamicsApiConfig, oauthClient } = createConfiguredClient()
72
+
73
+ await dynamicsApiConfig.onTokenRefresh()
74
+
75
+ expect(oauthClient.getToken).toHaveBeenCalledTimes(1)
76
+ })
77
+
78
+ it('calls getToken with scope when access token cache is empty', async () => {
79
+ const { dynamicsApiConfig, oauthClient } = createConfiguredClient()
80
+
81
+ await dynamicsApiConfig.onTokenRefresh()
82
+
83
+ expect(oauthClient.getToken).toHaveBeenCalledWith({ scope: MOCK_SCOPE })
84
+ })
85
+
86
+ it('calls accessToken.expired with PREEMPTIVE_TOKEN_EXPIRY_SECONDS when token is cached', async () => {
87
+ const { dynamicsApiConfig, oauthClient } = createConfiguredClient()
88
+ await seedCachedToken(dynamicsApiConfig)
89
+ const cachedAccessToken = await oauthClient.getToken.mock.results[0].value
90
+
91
+ await dynamicsApiConfig.onTokenRefresh()
92
+
93
+ expect(cachedAccessToken.expired).toHaveBeenCalledWith(PREEMPTIVE_TOKEN_EXPIRY_SECONDS)
94
+ })
95
+
96
+ it('returns cached token when cached access token is not expired', async () => {
97
+ const { dynamicsApiConfig } = createConfiguredClient()
98
+ await seedCachedToken(dynamicsApiConfig, 'FIRST TOKEN')
99
+ SimpleOAuth2.__setMockTokenReturnValue('SECOND TOKEN')
100
+
101
+ const token = await dynamicsApiConfig.onTokenRefresh()
102
+
103
+ expect(token).toBe('FIRST TOKEN')
104
+ })
105
+
106
+ it('does not call getToken again when cached access token is not expired', async () => {
107
+ const { dynamicsApiConfig, oauthClient } = createConfiguredClient()
108
+ await seedCachedToken(dynamicsApiConfig)
109
+
110
+ await dynamicsApiConfig.onTokenRefresh()
111
+
112
+ expect(oauthClient.getToken).toHaveBeenCalledTimes(1)
113
+ })
114
+
115
+ it('returns refreshed token when cached access token is expired', async () => {
116
+ const { dynamicsApiConfig } = createConfiguredClient()
117
+ await seedCachedToken(dynamicsApiConfig, 'FIRST TOKEN')
118
+ SimpleOAuth2.__setMockTokenReturnValue('NEW TOKEN')
35
119
  SimpleOAuth2.__setMockTokenExpired(true)
36
- await dynamicsApiConfig.onTokenRefresh(testCallback)
37
- expect(testCallback).toHaveBeenLastCalledWith('NEW MOCK TOKEN')
120
+
121
+ const token = await dynamicsApiConfig.onTokenRefresh()
122
+
123
+ expect(token).toBe('NEW TOKEN')
124
+ })
125
+
126
+ it('calls getToken again when cached access token is expired', async () => {
127
+ const { dynamicsApiConfig, oauthClient } = createConfiguredClient()
128
+ await seedCachedToken(dynamicsApiConfig)
129
+ SimpleOAuth2.__setMockTokenExpired(true)
130
+
131
+ await dynamicsApiConfig.onTokenRefresh()
132
+
133
+ expect(oauthClient.getToken).toHaveBeenCalledTimes(2)
134
+ })
135
+
136
+ it('calls getToken with scope when cached access token is expired', async () => {
137
+ const { dynamicsApiConfig, oauthClient } = createConfiguredClient()
138
+ await seedCachedToken(dynamicsApiConfig)
139
+ SimpleOAuth2.__setMockTokenExpired(true)
140
+
141
+ await dynamicsApiConfig.onTokenRefresh()
142
+
143
+ expect(oauthClient.getToken).toHaveBeenLastCalledWith({ scope: MOCK_SCOPE })
38
144
  })
39
145
  })
@@ -13,7 +13,7 @@ import {
13
13
  } from '../../index.js'
14
14
  import TestEntity from '../../__mocks__/TestEntity.js'
15
15
  import { v4 as uuidv4 } from 'uuid'
16
- import MockDynamicsWebApi from 'dynamics-web-api'
16
+ import { DynamicsWebApi as MockDynamicsWebApi } from 'dynamics-web-api'
17
17
  import { PredefinedQuery } from '../../queries/predefined-query.js'
18
18
  import { BaseEntity, EntityDefinition } from '../../entities/base.entity.js'
19
19
 
@@ -33,7 +33,7 @@ describe('entity manager', () => {
33
33
  t.boolVal = true
34
34
 
35
35
  const result = await persist([t])
36
- expect(MockDynamicsWebApi.prototype.createRequest).toHaveBeenCalled()
36
+ expect(MockDynamicsWebApi.prototype.create).toHaveBeenCalled()
37
37
  expect(result).toHaveLength(1)
38
38
  expect(result[0]).toEqual(resultUuid)
39
39
  })
@@ -54,7 +54,7 @@ describe('entity manager', () => {
54
54
  )
55
55
 
56
56
  const result = await persist([t])
57
- expect(MockDynamicsWebApi.prototype.updateRequest).toHaveBeenCalled()
57
+ expect(MockDynamicsWebApi.prototype.update).toHaveBeenCalled()
58
58
  expect(result).toHaveLength(1)
59
59
  expect(result[0]).toEqual(resultUuid)
60
60
  })
@@ -69,7 +69,7 @@ describe('entity manager', () => {
69
69
  t.boolVal = true
70
70
 
71
71
  const result = await persist([t], 'foo')
72
- expect(MockDynamicsWebApi.prototype.createRequest).toHaveBeenCalled()
72
+ expect(MockDynamicsWebApi.prototype.create).toHaveBeenCalled()
73
73
  expect(MockDynamicsWebApi.prototype.executeBatch).toHaveBeenCalledWith(
74
74
  expect.objectContaining({
75
75
  impersonateAAD: 'foo'
@@ -94,10 +94,10 @@ describe('entity manager', () => {
94
94
  {}
95
95
  )
96
96
  await expect(persist([newEntity, existingEntity])).rejects.toThrow('Test error')
97
- // Expect the console error to contain details of the batch data (one createRequest, one updateRequest plus the exception object)
97
+ // Expect the console error to contain details of the batch data (one create, one update plus the exception object)
98
98
  expect(consoleErrorSpy).toHaveBeenCalledWith(
99
99
  expect.stringMatching('Error persisting batch. Data: %j, Exception: %o'),
100
- expect.arrayContaining([{ createRequest: expect.any(Object) }, { updateRequest: expect.any(Object) }]),
100
+ expect.arrayContaining([{ create: expect.any(Object) }, { update: expect.any(Object) }]),
101
101
  expect.any(Error)
102
102
  )
103
103
  })
@@ -332,14 +332,14 @@ describe('entity manager', () => {
332
332
 
333
333
  describe('findById', () => {
334
334
  it('finds by a primary key guid', async () => {
335
- MockDynamicsWebApi.__setResponse('retrieveRequest', {
335
+ MockDynamicsWebApi.__setResponse('retrieve', {
336
336
  '@odata.etag': 'W/"202465000"',
337
337
  idval: '9f1b34a0-0c66-e611-80dc-c4346bad0190',
338
338
  strval: 'example'
339
339
  })
340
340
 
341
341
  const result = await findById(TestEntity, '9f1b34a0-0c66-e611-80dc-c4346bad0190')
342
- expect(MockDynamicsWebApi.prototype.retrieveRequest).toBeCalledWith({
342
+ expect(MockDynamicsWebApi.prototype.retrieve).toBeCalledWith({
343
343
  key: '9f1b34a0-0c66-e611-80dc-c4346bad0190',
344
344
  collection: TestEntity.definition.dynamicsCollection,
345
345
  select: TestEntity.definition.select
@@ -350,13 +350,13 @@ describe('entity manager', () => {
350
350
  it('returns null if not found', async () => {
351
351
  const notFoundError = new Error('Not found')
352
352
  notFoundError.status = 404
353
- MockDynamicsWebApi.__throwWithErrorOn('retrieveRequest', notFoundError)
353
+ MockDynamicsWebApi.__throwWithErrorOn('retrieve', notFoundError)
354
354
  const result = await findById(TestEntity, '9f1b34a0-0c66-e611-80dc-c4346bad0190')
355
355
  expect(result).toEqual(null)
356
356
  })
357
357
 
358
358
  it('throws an exception on general errors', async () => {
359
- MockDynamicsWebApi.__throwWithErrorOn('retrieveRequest')
359
+ MockDynamicsWebApi.__throwWithErrorOn('retrieve')
360
360
  const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(jest.fn())
361
361
  await expect(findById(TestEntity, '9f1b34a0-0c66-e611-80dc-c4346bad0190')).rejects.toThrow('Test error')
362
362
  expect(consoleErrorSpy).toHaveBeenCalled()
@@ -365,13 +365,13 @@ describe('entity manager', () => {
365
365
 
366
366
  describe('findByAlternateKey', () => {
367
367
  it('finds by an alternate key', async () => {
368
- MockDynamicsWebApi.__setResponse('retrieveRequest', {
368
+ MockDynamicsWebApi.__setResponse('retrieve', {
369
369
  '@odata.etag': 'W/"202465000"',
370
370
  idval: '9f1b34a0-0c66-e611-80dc-c4346bad0190',
371
371
  strval: 'example'
372
372
  })
373
373
  const result = await findByAlternateKey(TestEntity, 'example')
374
- expect(MockDynamicsWebApi.prototype.retrieveRequest).toBeCalledWith({
374
+ expect(MockDynamicsWebApi.prototype.retrieve).toBeCalledWith({
375
375
  key: "strval='example'",
376
376
  collection: TestEntity.definition.dynamicsCollection,
377
377
  select: TestEntity.definition.select
@@ -381,13 +381,13 @@ describe('entity manager', () => {
381
381
  })
382
382
 
383
383
  it('escapes special characters in the key', async () => {
384
- MockDynamicsWebApi.__setResponse('retrieveRequest', {
384
+ MockDynamicsWebApi.__setResponse('retrieve', {
385
385
  '@odata.etag': 'W/"202465000"',
386
386
  idval: '9f1b34a0-0c66-e611-80dc-c4346bad0190',
387
387
  strval: 'example'
388
388
  })
389
389
  const result = await findByAlternateKey(TestEntity, "test & example'")
390
- expect(MockDynamicsWebApi.prototype.retrieveRequest).toBeCalledWith({
390
+ expect(MockDynamicsWebApi.prototype.retrieve).toBeCalledWith({
391
391
  key: "strval='test %26 example'''",
392
392
  collection: TestEntity.definition.dynamicsCollection,
393
393
  select: TestEntity.definition.select
@@ -399,7 +399,7 @@ describe('entity manager', () => {
399
399
 
400
400
  describe('findByExample', () => {
401
401
  it('builds a select statement appropriate to the type definition for each field', async () => {
402
- MockDynamicsWebApi.__setResponse('retrieveMultipleRequest', { value: [{}] })
402
+ MockDynamicsWebApi.__setResponse('retrieveMultiple', { value: [{}] })
403
403
 
404
404
  const lookup = new TestEntity()
405
405
  lookup.strVal = 'StringData'
@@ -412,7 +412,7 @@ describe('entity manager', () => {
412
412
  const expectedLookupSelect =
413
413
  "strval eq 'StringData' and intval eq 123 and decval eq 123.45 and boolval eq true and dateval eq 1946-01-01 and datetimeval eq 1946-01-01T01:02:03Z and optionsetval eq 910400000"
414
414
  const result = await findByExample(lookup)
415
- expect(MockDynamicsWebApi.prototype.retrieveMultipleRequest).toBeCalledWith({
415
+ expect(MockDynamicsWebApi.prototype.retrieveMultiple).toBeCalledWith({
416
416
  collection: TestEntity.definition.dynamicsCollection,
417
417
  select: TestEntity.definition.select,
418
418
  filter: expect.stringMatching(`${TestEntity.definition.defaultFilter} and ${expectedLookupSelect}`)
@@ -422,7 +422,7 @@ describe('entity manager', () => {
422
422
  })
423
423
 
424
424
  it('does not require the entity to define a default filter', async () => {
425
- MockDynamicsWebApi.__setResponse('retrieveMultipleRequest', { value: [{}] })
425
+ MockDynamicsWebApi.__setResponse('retrieveMultiple', { value: [{}] })
426
426
 
427
427
  class SameEntity extends BaseEntity {
428
428
  static _definition = new EntityDefinition(() => ({
@@ -458,7 +458,7 @@ describe('entity manager', () => {
458
458
  lookup.testVal = 'StringData'
459
459
  const expectedLookupSelect = "testval eq 'StringData'"
460
460
  const result = await findByExample(lookup)
461
- expect(MockDynamicsWebApi.prototype.retrieveMultipleRequest).toBeCalledWith({
461
+ expect(MockDynamicsWebApi.prototype.retrieveMultiple).toBeCalledWith({
462
462
  collection: SameEntity.definition.dynamicsCollection,
463
463
  select: SameEntity.definition.select,
464
464
  filter: expect.stringMatching(`${expectedLookupSelect}`)
@@ -468,13 +468,13 @@ describe('entity manager', () => {
468
468
  })
469
469
 
470
470
  it('only serializes fields into the select statement if they are set', async () => {
471
- MockDynamicsWebApi.__setResponse('retrieveMultipleRequest', { value: [{}] })
471
+ MockDynamicsWebApi.__setResponse('retrieveMultiple', { value: [{}] })
472
472
 
473
473
  const lookup = new TestEntity()
474
474
  lookup.strVal = 'StringData'
475
475
  const expectedLookupSelect = "strval eq 'StringData'"
476
476
  const result = await findByExample(lookup)
477
- expect(MockDynamicsWebApi.prototype.retrieveMultipleRequest).toBeCalledWith({
477
+ expect(MockDynamicsWebApi.prototype.retrieveMultiple).toBeCalledWith({
478
478
  collection: TestEntity.definition.dynamicsCollection,
479
479
  select: TestEntity.definition.select,
480
480
  filter: expect.stringMatching(`${TestEntity.definition.defaultFilter} and ${expectedLookupSelect}`)
@@ -484,7 +484,7 @@ describe('entity manager', () => {
484
484
  })
485
485
 
486
486
  it('throws an error object on failure', async () => {
487
- MockDynamicsWebApi.__throwWithErrorOn('retrieveMultipleRequest')
487
+ MockDynamicsWebApi.__throwWithErrorOn('retrieveMultiple')
488
488
  const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(jest.fn())
489
489
  await expect(findByExample(new TestEntity())).rejects.toThrow('Test error')
490
490
  expect(consoleErrorSpy).toHaveBeenCalled()
@@ -492,8 +492,8 @@ describe('entity manager', () => {
492
492
  })
493
493
 
494
494
  describe('executeQuery', () => {
495
- it('calls retrieveMultipleRequest on the Dynamics API with the request data', async () => {
496
- MockDynamicsWebApi.__setResponse('retrieveMultipleRequest', {
495
+ it('calls retrieveMultiple on the Dynamics API with the request data', async () => {
496
+ MockDynamicsWebApi.__setResponse('retrieveMultiple', {
497
497
  value: [
498
498
  {
499
499
  '@odata.etag': 'W/"202465000"',
@@ -504,7 +504,7 @@ describe('entity manager', () => {
504
504
  })
505
505
 
506
506
  const result = await executeQuery(new PredefinedQuery({ root: TestEntity, filter: "strval eq 'example'" }))
507
- expect(MockDynamicsWebApi.prototype.retrieveMultipleRequest).toBeCalledWith({
507
+ expect(MockDynamicsWebApi.prototype.retrieveMultiple).toBeCalledWith({
508
508
  collection: TestEntity.definition.dynamicsCollection,
509
509
  filter: "strval eq 'example'",
510
510
  select: TestEntity.definition.select
@@ -516,7 +516,7 @@ describe('entity manager', () => {
516
516
  })
517
517
 
518
518
  it('throws an error object on failure', async () => {
519
- MockDynamicsWebApi.__throwWithErrorOn('retrieveMultipleRequest')
519
+ MockDynamicsWebApi.__throwWithErrorOn('retrieveMultiple')
520
520
  const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(jest.fn())
521
521
  await expect(executeQuery(new PredefinedQuery({ root: TestEntity, filter: "strval eq 'example'" }))).rejects.toThrow('Test error')
522
522
  expect(consoleErrorSpy).toHaveBeenCalled()
@@ -527,7 +527,7 @@ describe('entity manager', () => {
527
527
  beforeEach(async () => {
528
528
  MockDynamicsWebApi.__reset()
529
529
  MockDynamicsWebApi.__setNextResponses(
530
- 'retrieveMultipleRequest',
530
+ 'retrieveMultiple',
531
531
  {
532
532
  value: [
533
533
  {
@@ -592,7 +592,7 @@ describe('entity manager', () => {
592
592
  })
593
593
 
594
594
  it('throws an error object on failure', async () => {
595
- MockDynamicsWebApi.__throwWithErrorOn('retrieveMultipleRequest')
595
+ MockDynamicsWebApi.__throwWithErrorOn('retrieveMultiple')
596
596
  const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(jest.fn())
597
597
  await expect(
598
598
  executePagedQuery(new PredefinedQuery({ root: TestEntity, filter: "strval eq 'example'" }), () => {}, 1)
@@ -1,4 +1,4 @@
1
- import DynamicsWebApi from 'dynamics-web-api'
1
+ import { DynamicsWebApi } from 'dynamics-web-api'
2
2
  import SimpleOAuth2 from 'simple-oauth2'
3
3
  const PREEMPTIVE_TOKEN_EXPIRY_SECONDS = 60
4
4
 
@@ -21,14 +21,16 @@ export function config () {
21
21
 
22
22
  let accessToken = null
23
23
  return {
24
- webApiUrl: process.env.DYNAMICS_API_PATH,
25
- webApiVersion: process.env.DYNAMICS_API_VERSION,
24
+ serverUrl: process.env.DYNAMICS_API_PATH,
25
+ dataApi: {
26
+ version: process.env.DYNAMICS_API_VERSION
27
+ },
26
28
  timeout: process.env.DYNAMICS_API_TIMEOUT || 90000,
27
- onTokenRefresh: async dynamicsWebApiCallback => {
29
+ onTokenRefresh: async () => {
28
30
  if (!accessToken || accessToken.expired(PREEMPTIVE_TOKEN_EXPIRY_SECONDS)) {
29
31
  accessToken = await oauthClient.getToken({ scope: process.env.OAUTH_SCOPE })
30
32
  }
31
- dynamicsWebApiCallback(accessToken.token.access_token)
33
+ return accessToken.token.access_token
32
34
  }
33
35
  }
34
36
  }
@@ -14,9 +14,9 @@ export async function persist (entities, createdBy) {
14
14
  dynamicsClient.startBatch()
15
15
  entities.forEach(entity => {
16
16
  if (entity.isNew()) {
17
- dynamicsClient.createRequest(entity.toPersistRequest())
17
+ dynamicsClient.create(entity.toPersistRequest())
18
18
  } else {
19
- dynamicsClient.updateRequest(entity.toPersistRequest())
19
+ dynamicsClient.update(entity.toPersistRequest())
20
20
  }
21
21
  })
22
22
  return await dynamicsClient.executeBatch({
@@ -24,7 +24,7 @@ export async function persist (entities, createdBy) {
24
24
  })
25
25
  } catch (e) {
26
26
  const error = e.length ? e[0] : e
27
- const requestDetails = entities.map(entity => ({ [entity.isNew() ? 'createRequest' : 'updateRequest']: entity.toPersistRequest() }))
27
+ const requestDetails = entities.map(entity => ({ [entity.isNew() ? 'create' : 'update']: entity.toPersistRequest() }))
28
28
  console.error('Error persisting batch. Data: %j, Exception: %o', requestDetails, error)
29
29
  throw error
30
30
  }
@@ -33,7 +33,7 @@ export async function persist (entities, createdBy) {
33
33
  const retrieveMultipleFetchOperation = async entityClasses => {
34
34
  try {
35
35
  dynamicsClient.startBatch()
36
- entityClasses.forEach(cls => dynamicsClient.retrieveMultipleRequest(cls.definition.toRetrieveRequest()))
36
+ entityClasses.forEach(cls => dynamicsClient.retrieveMultiple(cls.definition.toRetrieveRequest()))
37
37
  return await dynamicsClient.executeBatch()
38
38
  } catch (e) {
39
39
  const error = e.length ? e[0] : e
@@ -98,7 +98,10 @@ export function retrieveGlobalOptionSets () {
98
98
  'dynamics_optionsetmap',
99
99
  async () => {
100
100
  try {
101
- const data = await dynamicsClient.retrieveGlobalOptionSets('Microsoft.Dynamics.CRM.OptionSetMetadata', ['Name', 'Options'])
101
+ const data = await dynamicsClient.retrieveGlobalOptionSets({
102
+ castType: 'Microsoft.Dynamics.CRM.OptionSetMetadata',
103
+ select: ['Name', 'Options']
104
+ })
102
105
  return data.value.reduce((acc, { Name: name, Options: options }) => {
103
106
  acc[name] = {
104
107
  name,
@@ -132,7 +135,7 @@ export function retrieveGlobalOptionSets () {
132
135
  */
133
136
  export async function findById (entityType, key) {
134
137
  try {
135
- const record = await dynamicsClient.retrieveRequest({ key: key, ...entityType.definition.toRetrieveRequest(null) })
138
+ const record = await dynamicsClient.retrieve({ key: key, ...entityType.definition.toRetrieveRequest(null) })
136
139
  const optionSetData = await retrieveGlobalOptionSets().cached()
137
140
  return entityType.fromResponse(record, optionSetData)
138
141
  } catch (e) {
@@ -180,7 +183,7 @@ export async function findByExample (entity) {
180
183
  return acc
181
184
  }, [])
182
185
  ].join(' and ')
183
- const results = await dynamicsClient.retrieveMultipleRequest(entity.constructor.definition.toRetrieveRequest(filter))
186
+ const results = await dynamicsClient.retrieveMultiple(entity.constructor.definition.toRetrieveRequest(filter))
184
187
  const optionSetData = await retrieveGlobalOptionSets().cached()
185
188
  return results.value.map(result => entity.constructor.fromResponse(result, optionSetData))
186
189
  } catch (e) {
@@ -200,7 +203,7 @@ export async function findByExample (entity) {
200
203
  */
201
204
  export async function executeQuery (query) {
202
205
  try {
203
- const response = await dynamicsClient.retrieveMultipleRequest(query.toRetrieveRequest())
206
+ const response = await dynamicsClient.retrieveMultiple(query.toRetrieveRequest())
204
207
  const optionSetData = await retrieveGlobalOptionSets().cached()
205
208
  return query.fromResponse(response.value, optionSetData)
206
209
  } catch (e) {
@@ -227,7 +230,7 @@ export async function executePagedQuery (query, onPageReceived, maxPages) {
227
230
  const optionSetData = await retrieveGlobalOptionSets().cached()
228
231
  let nextLink = null
229
232
  do {
230
- const response = await dynamicsClient.retrieveMultipleRequest(query.toRetrieveRequest(), nextLink)
233
+ const response = await dynamicsClient.retrieveMultiple(query.toRetrieveRequest(), nextLink)
231
234
  nextLink = response.oDataNextLink
232
235
  await onPageReceived(query.fromResponse(response.value, optionSetData))
233
236
  processed += response.value.length
@@ -308,7 +308,7 @@ export class BaseEntity {
308
308
  ...(!this.isNew() && { key: this.id }),
309
309
  collection: this.constructor.definition.dynamicsCollection,
310
310
  contentId: this.uniqueContentId,
311
- entity: this.toRequestBody()
311
+ data: this.toRequestBody()
312
312
  }
313
313
  }
314
314
 
@@ -4,11 +4,13 @@ import { Permission } from '../../entities/permission.entity.js'
4
4
  import { PredefinedQuery } from '../predefined-query.js'
5
5
 
6
6
  jest.mock('dynamics-web-api', () => {
7
- return jest.fn().mockImplementation(() => {
8
- return {
9
- executeUnboundAction: jest.fn()
10
- }
11
- })
7
+ return {
8
+ DynamicsWebApi: jest.fn().mockImplementation(() => {
9
+ return {
10
+ callAction: jest.fn()
11
+ }
12
+ })
13
+ }
12
14
  })
13
15
 
14
16
  describe('Contact Queries', () => {