@defra-fish/pocl-job 1.61.0-rc.9 → 1.61.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,10 +1,10 @@
1
1
  {
2
2
  "name": "@defra-fish/pocl-job",
3
- "version": "1.61.0-rc.9",
3
+ "version": "1.61.0",
4
4
  "description": "Post Office Counter Licence sales processor",
5
5
  "type": "module",
6
6
  "engines": {
7
- "node": ">=18.17"
7
+ "node": ">=20"
8
8
  },
9
9
  "keywords": [
10
10
  "rod",
@@ -35,8 +35,8 @@
35
35
  "test": "echo \"Error: run tests from root\" && exit 1"
36
36
  },
37
37
  "dependencies": {
38
- "@defra-fish/business-rules-lib": "1.61.0-rc.9",
39
- "@defra-fish/connectors-lib": "1.61.0-rc.9",
38
+ "@defra-fish/business-rules-lib": "1.61.0",
39
+ "@defra-fish/connectors-lib": "1.61.0",
40
40
  "commander": "^7.2.0",
41
41
  "debug": "^4.3.3",
42
42
  "filesize": "^6.4.0",
@@ -45,5 +45,5 @@
45
45
  "moment-timezone": "^0.5.34",
46
46
  "sax-stream": "^1.3.0"
47
47
  },
48
- "gitHead": "b9c65c7827bb974c94056bf5e387d80564d3ddc6"
48
+ "gitHead": "8d1841265a40178f285288e5323a36e887483434"
49
49
  }
@@ -1,12 +1,7 @@
1
- import AwsMock from 'aws-sdk'
2
1
  import config from '../config.js'
3
2
 
4
3
  describe('config', () => {
5
4
  beforeAll(async () => {
6
- AwsMock.SecretsManager.__setResponse('getSecretValue', {
7
- SecretString: 'test-ssh-key'
8
- })
9
-
10
5
  process.env.POCL_FILE_STAGING_TABLE = 'test-file-staging-table'
11
6
  process.env.POCL_RECORD_STAGING_TABLE = 'test-record-staging-table'
12
7
  process.env.POCL_STAGING_TTL = 1234
@@ -1,5 +1,6 @@
1
1
  import * as db from '../db.js'
2
- import AwsMock from 'aws-sdk'
2
+ import { AWS } from '@defra-fish/connectors-lib'
3
+ const { docClient } = AWS.mock.results[0].value
3
4
 
4
5
  jest.mock('../../config.js', () => ({
5
6
  db: {
@@ -9,16 +10,28 @@ jest.mock('../../config.js', () => ({
9
10
  }
10
11
  }))
11
12
 
13
+ jest.mock('@defra-fish/connectors-lib', () => ({
14
+ AWS: jest.fn(() => ({
15
+ docClient: {
16
+ batchWriteAllPromise: jest.fn(),
17
+ get: jest.fn(() => ({ Item: undefined })),
18
+ scanAllPromise: jest.fn(),
19
+ update: jest.fn(),
20
+ createUpdateExpression: jest.fn(() => ({})),
21
+ queryAllPromise: jest.fn()
22
+ }
23
+ }))
24
+ }))
25
+
12
26
  describe('database operations', () => {
13
27
  const TEST_FILENAME = 'testfile.xml'
14
28
  beforeEach(() => {
15
29
  jest.clearAllMocks()
16
- AwsMock.__resetAll()
17
30
  })
18
31
  describe('getFileRecord', () => {
19
32
  it('calls a get operation on dynamodb', async () => {
20
33
  await db.getFileRecord(TEST_FILENAME)
21
- expect(AwsMock.DynamoDB.DocumentClient.mockedMethods.get).toHaveBeenCalledWith({
34
+ expect(docClient.get).toHaveBeenCalledWith({
22
35
  TableName: 'TestFileTable',
23
36
  Key: { filename: TEST_FILENAME },
24
37
  ConsistentRead: true
@@ -29,7 +42,7 @@ describe('database operations', () => {
29
42
  describe('getFileRecords', () => {
30
43
  it('retrieves all records for the given file if no stages are provided', async () => {
31
44
  await db.getFileRecords()
32
- expect(AwsMock.DynamoDB.DocumentClient.mockedMethods.scan).toHaveBeenCalledWith(
45
+ expect(docClient.scanAllPromise).toHaveBeenCalledWith(
33
46
  expect.objectContaining({
34
47
  TableName: 'TestFileTable',
35
48
  ConsistentRead: true
@@ -39,7 +52,7 @@ describe('database operations', () => {
39
52
 
40
53
  it('retrieves all records a given set of stages', async () => {
41
54
  await db.getFileRecords('STAGE 1', 'STAGE 2')
42
- expect(AwsMock.DynamoDB.DocumentClient.mockedMethods.scan).toHaveBeenCalledWith(
55
+ expect(docClient.scanAllPromise).toHaveBeenCalledWith(
43
56
  expect.objectContaining({
44
57
  TableName: 'TestFileTable',
45
58
  FilterExpression: 'stage IN (:stage0,:stage1)',
@@ -52,8 +65,21 @@ describe('database operations', () => {
52
65
 
53
66
  describe('updateFileStagingTable', () => {
54
67
  it('calls update on dynamodb including all necessary parameters', async () => {
68
+ docClient.createUpdateExpression.mockReturnValueOnce({
69
+ UpdateExpression: 'SET #expires = :expires,#param1 = :param1,#param2 = :param2',
70
+ ExpressionAttributeNames: {
71
+ '#expires': 'expires',
72
+ '#param1': 'param1',
73
+ '#param2': 'param2'
74
+ },
75
+ ExpressionAttributeValues: {
76
+ ':expires': expect.any(Number),
77
+ ':param1': 'test1',
78
+ ':param2': 'test2'
79
+ }
80
+ })
55
81
  await db.updateFileStagingTable({ filename: TEST_FILENAME, param1: 'test1', param2: 'test2' })
56
- expect(AwsMock.DynamoDB.DocumentClient.mockedMethods.update).toHaveBeenCalledWith(
82
+ expect(docClient.update).toHaveBeenCalledWith(
57
83
  expect.objectContaining({
58
84
  TableName: 'TestFileTable',
59
85
  Key: { filename: TEST_FILENAME },
@@ -77,7 +103,7 @@ describe('database operations', () => {
77
103
  it('calls batchWrite on dynamodb including all necessary parameters', async () => {
78
104
  const records = [{ id: 'test1' }, { id: 'test2' }]
79
105
  await db.updateRecordStagingTable(TEST_FILENAME, records)
80
- expect(AwsMock.DynamoDB.DocumentClient.mockedMethods.batchWrite).toHaveBeenCalledWith(
106
+ expect(docClient.batchWriteAllPromise).toHaveBeenCalledWith(
81
107
  expect.objectContaining({
82
108
  RequestItems: {
83
109
  TestRecordTable: [
@@ -99,14 +125,14 @@ describe('database operations', () => {
99
125
 
100
126
  it('is a no-op if records is empty', async () => {
101
127
  await db.updateRecordStagingTable(TEST_FILENAME, [])
102
- expect(AwsMock.DynamoDB.DocumentClient.mockedMethods.batchWrite).not.toHaveBeenCalled()
128
+ expect(docClient.batchWriteAllPromise).not.toHaveBeenCalled()
103
129
  })
104
130
  })
105
131
 
106
132
  describe('getProcessedRecords', () => {
107
133
  it('retrieves all records for the given file if no stages are provided', async () => {
108
134
  await db.getProcessedRecords(TEST_FILENAME)
109
- expect(AwsMock.DynamoDB.DocumentClient.mockedMethods.query).toHaveBeenCalledWith(
135
+ expect(docClient.queryAllPromise).toHaveBeenCalledWith(
110
136
  expect.objectContaining({
111
137
  TableName: 'TestRecordTable',
112
138
  KeyConditionExpression: 'filename = :filename',
@@ -118,7 +144,7 @@ describe('database operations', () => {
118
144
 
119
145
  it('retrieves all records a given set of stages', async () => {
120
146
  await db.getProcessedRecords(TEST_FILENAME, 'STAGE 1', 'STAGE 2')
121
- expect(AwsMock.DynamoDB.DocumentClient.mockedMethods.query).toHaveBeenCalledWith(
147
+ expect(docClient.queryAllPromise).toHaveBeenCalledWith(
122
148
  expect.objectContaining({
123
149
  TableName: 'TestRecordTable',
124
150
  KeyConditionExpression: 'filename = :filename',
@@ -2,19 +2,31 @@ import { refreshS3Metadata } from '../s3'
2
2
  import moment from 'moment'
3
3
  import { updateFileStagingTable } from '../../io/db.js'
4
4
  import { DYNAMICS_IMPORT_STAGE, FILE_STAGE, POST_OFFICE_DATASOURCE } from '../../staging/constants.js'
5
- import { salesApi } from '@defra-fish/connectors-lib'
5
+ import { salesApi, AWS } from '@defra-fish/connectors-lib'
6
6
  import fs from 'fs'
7
- import AwsMock from 'aws-sdk'
7
+ const { s3, ListObjectsV2Command } = AWS.mock.results[0].value
8
8
 
9
- jest.mock('fs')
10
9
  jest.mock('md5-file')
11
10
  jest.mock('../../io/db.js')
12
11
  jest.mock('../../io/file.js')
13
12
 
14
13
  jest.mock('@defra-fish/connectors-lib', () => {
15
14
  const actual = jest.requireActual('@defra-fish/connectors-lib')
15
+ const AWS = jest.fn(() => ({
16
+ docClient: {
17
+ update: jest.fn(),
18
+ createUpdateExpression: jest.fn(() => ({}))
19
+ },
20
+ s3: {
21
+ getObject: jest.fn(() => ({
22
+ createReadStream: jest.fn()
23
+ })),
24
+ send: jest.fn()
25
+ },
26
+ ListObjectsV2Command: jest.fn()
27
+ }))
16
28
  return {
17
- AWS: actual.AWS,
29
+ AWS,
18
30
  salesApi: {
19
31
  ...Object.keys(actual.salesApi).reduce((acc, k) => ({ ...acc, [k]: jest.fn(async () => {}) }), {})
20
32
  }
@@ -29,19 +41,19 @@ jest.mock('../../config.js', () => ({
29
41
  bucket: 'testbucket'
30
42
  }
31
43
  }))
44
+
32
45
  describe('s3 operations', () => {
33
46
  beforeEach(() => {
34
47
  jest.clearAllMocks()
35
- AwsMock.__resetAll()
36
48
  })
37
49
 
38
50
  describe('refreshS3Metadata', () => {
39
- it('gets a list of files from S3', async () => {
51
+ describe('gets a list of files from S3', () => {
40
52
  const s3Key1 = `${moment().format('YYYY-MM-DD')}/test1.xml`
41
53
  const s3Key2 = `${moment().format('YYYY-MM-DD')}/test2.xml`
42
54
 
43
- AwsMock.S3.mockedMethods.listObjectsV2.mockReturnValueOnce({
44
- promise: () => ({
55
+ beforeEach(async () => {
56
+ s3.send.mockReturnValueOnce({
45
57
  IsTruncated: false,
46
58
  Contents: [
47
59
  {
@@ -58,52 +70,66 @@ describe('s3 operations', () => {
58
70
  }
59
71
  ]
60
72
  })
61
- })
62
73
 
63
- await refreshS3Metadata()
74
+ await refreshS3Metadata()
75
+ })
64
76
 
65
- expect(AwsMock.S3.mockedMethods.listObjectsV2).toHaveBeenNthCalledWith(1, {
66
- Bucket: 'testbucket',
67
- ContinuationToken: undefined
77
+ it('calls ListObjectsV2Command, with bucket name and no continuation token', () => {
78
+ expect(ListObjectsV2Command).toHaveBeenNthCalledWith(1, {
79
+ Bucket: 'testbucket',
80
+ ContinuationToken: undefined
81
+ })
68
82
  })
69
- expect(updateFileStagingTable).toHaveBeenNthCalledWith(1, {
70
- filename: 'test1.xml',
71
- md5: 'example-md5',
72
- fileSize: '1 KB',
73
- stage: FILE_STAGE.Pending,
74
- s3Key: s3Key1
83
+
84
+ it('calls updateFileStagingTable first with initial test file', () => {
85
+ expect(updateFileStagingTable).toHaveBeenNthCalledWith(1, {
86
+ filename: 'test1.xml',
87
+ md5: 'example-md5',
88
+ fileSize: '1 KB',
89
+ stage: FILE_STAGE.Pending,
90
+ s3Key: s3Key1
91
+ })
75
92
  })
76
- expect(updateFileStagingTable).toHaveBeenNthCalledWith(2, {
77
- filename: 'test2.xml',
78
- md5: 'example-md5',
79
- fileSize: '2 KB',
80
- stage: FILE_STAGE.Pending,
81
- s3Key: s3Key2
93
+
94
+ it('calls updateFileStagingTable a second time with second test file', () => {
95
+ expect(updateFileStagingTable).toHaveBeenNthCalledWith(2, {
96
+ filename: 'test2.xml',
97
+ md5: 'example-md5',
98
+ fileSize: '2 KB',
99
+ stage: FILE_STAGE.Pending,
100
+ s3Key: s3Key2
101
+ })
82
102
  })
83
- expect(salesApi.upsertTransactionFile).toHaveBeenNthCalledWith(1, 'test1.xml', {
84
- status: DYNAMICS_IMPORT_STAGE.Pending,
85
- dataSource: POST_OFFICE_DATASOURCE,
86
- fileSize: '1 KB',
87
- receiptTimestamp: expect.any(String),
88
- salesDate: expect.any(String),
89
- notes: 'Retrieved from the remote server and awaiting processing'
103
+
104
+ it('calls upsertTransactionFile for first test file', () => {
105
+ expect(salesApi.upsertTransactionFile).toHaveBeenNthCalledWith(1, 'test1.xml', {
106
+ status: DYNAMICS_IMPORT_STAGE.Pending,
107
+ dataSource: POST_OFFICE_DATASOURCE,
108
+ fileSize: '1 KB',
109
+ receiptTimestamp: expect.any(String),
110
+ salesDate: expect.any(String),
111
+ notes: 'Retrieved from the remote server and awaiting processing'
112
+ })
90
113
  })
91
- expect(salesApi.upsertTransactionFile).toHaveBeenNthCalledWith(2, 'test2.xml', {
92
- status: DYNAMICS_IMPORT_STAGE.Pending,
93
- dataSource: POST_OFFICE_DATASOURCE,
94
- fileSize: '2 KB',
95
- receiptTimestamp: expect.any(String),
96
- salesDate: expect.any(String),
97
- notes: 'Retrieved from the remote server and awaiting processing'
114
+
115
+ it('calls upsertTransactionFile for second test file', () => {
116
+ expect(salesApi.upsertTransactionFile).toHaveBeenNthCalledWith(2, 'test2.xml', {
117
+ status: DYNAMICS_IMPORT_STAGE.Pending,
118
+ dataSource: POST_OFFICE_DATASOURCE,
119
+ fileSize: '2 KB',
120
+ receiptTimestamp: expect.any(String),
121
+ salesDate: expect.any(String),
122
+ notes: 'Retrieved from the remote server and awaiting processing'
123
+ })
98
124
  })
99
125
  })
100
126
 
101
- it('gets a truncated list of files from S3', async () => {
127
+ describe('gets a truncated list of files from S3', () => {
102
128
  const s3Key1 = `${moment().format('YYYY-MM-DD')}/test1.xml`
103
129
 
104
- AwsMock.S3.mockedMethods.listObjectsV2
105
- .mockReturnValue({
106
- promise: () => ({
130
+ beforeEach(async () => {
131
+ s3.send
132
+ .mockReturnValue({
107
133
  IsTruncated: false,
108
134
  Contents: [
109
135
  {
@@ -114,9 +140,7 @@ describe('s3 operations', () => {
114
140
  }
115
141
  ]
116
142
  })
117
- })
118
- .mockReturnValueOnce({
119
- promise: () => ({
143
+ .mockReturnValueOnce({
120
144
  IsTruncated: true,
121
145
  NextContinuationToken: 'token',
122
146
  Contents: [
@@ -128,53 +152,72 @@ describe('s3 operations', () => {
128
152
  }
129
153
  ]
130
154
  })
131
- })
132
155
 
133
- await refreshS3Metadata()
156
+ await refreshS3Metadata()
157
+ })
134
158
 
135
- expect(AwsMock.S3.mockedMethods.listObjectsV2).toHaveBeenNthCalledWith(1, {
136
- Bucket: 'testbucket',
137
- ContinuationToken: undefined
159
+ it('calls ListObjectsV2Command a first time with bucket name and no continuation token', () => {
160
+ expect(ListObjectsV2Command).toHaveBeenNthCalledWith(1, {
161
+ Bucket: 'testbucket',
162
+ ContinuationToken: undefined
163
+ })
138
164
  })
139
- expect(AwsMock.S3.mockedMethods.listObjectsV2).toHaveBeenNthCalledWith(2, {
140
- Bucket: 'testbucket',
141
- ContinuationToken: 'token'
165
+
166
+ it('calls ListObjectsV2Command a second time with bucket name and continuation token', () => {
167
+ expect(ListObjectsV2Command).toHaveBeenNthCalledWith(2, {
168
+ Bucket: 'testbucket',
169
+ ContinuationToken: 'token'
170
+ })
142
171
  })
143
- expect(updateFileStagingTable).toHaveBeenNthCalledWith(1, {
144
- filename: 'test1.xml',
145
- md5: 'example-md5',
146
- fileSize: '1 KB',
147
- stage: FILE_STAGE.Pending,
148
- s3Key: s3Key1
172
+
173
+ it('updates file staging table with first test file', () => {
174
+ expect(updateFileStagingTable).toHaveBeenNthCalledWith(1, {
175
+ filename: 'test1.xml',
176
+ md5: 'example-md5',
177
+ fileSize: '1 KB',
178
+ stage: FILE_STAGE.Pending,
179
+ s3Key: s3Key1
180
+ })
149
181
  })
150
- expect(salesApi.upsertTransactionFile).toHaveBeenNthCalledWith(1, 'test1.xml', {
151
- status: DYNAMICS_IMPORT_STAGE.Pending,
152
- dataSource: POST_OFFICE_DATASOURCE,
153
- fileSize: '1 KB',
154
- receiptTimestamp: expect.any(String),
155
- salesDate: expect.any(String),
156
- notes: 'Retrieved from the remote server and awaiting processing'
182
+
183
+ it('updates file staging table with second test file', () => {
184
+ expect(updateFileStagingTable).toHaveBeenNthCalledWith(2, {
185
+ filename: 'test1.xml',
186
+ md5: 'example-md5',
187
+ fileSize: '1 KB',
188
+ stage: FILE_STAGE.Pending,
189
+ s3Key: s3Key1
190
+ })
191
+ })
192
+
193
+ it('upserts sales api with transaction file details', () => {
194
+ expect(salesApi.upsertTransactionFile).toHaveBeenNthCalledWith(1, 'test1.xml', {
195
+ status: DYNAMICS_IMPORT_STAGE.Pending,
196
+ dataSource: POST_OFFICE_DATASOURCE,
197
+ fileSize: '1 KB',
198
+ receiptTimestamp: expect.any(String),
199
+ salesDate: expect.any(String),
200
+ notes: 'Retrieved from the remote server and awaiting processing'
201
+ })
157
202
  })
158
203
  })
159
204
 
160
205
  it('skips file processing if a file has already been marked as processed in Dynamics', async () => {
161
- fs.createReadStream.mockReturnValueOnce('teststream')
162
- fs.statSync.mockReturnValueOnce({ size: 1024 })
206
+ jest.spyOn(fs, 'createReadStream').mockReturnValueOnce('teststream')
207
+ jest.spyOn(fs, 'statSync').mockReturnValueOnce({ size: 1024 })
163
208
  salesApi.getTransactionFile.mockResolvedValueOnce({ status: { description: 'Processed' } })
164
209
  const s3Key = `${moment().format('YYYY-MM-DD')}/test-already-processed.xml`
165
210
 
166
- AwsMock.S3.mockedMethods.listObjectsV2.mockReturnValueOnce({
167
- promise: () => ({
168
- IsTruncated: false,
169
- Contents: [
170
- {
171
- Key: s3Key,
172
- LastModified: moment().toISOString(),
173
- ETag: 'example-md5',
174
- Size: 1024
175
- }
176
- ]
177
- })
211
+ s3.send.mockReturnValueOnce({
212
+ IsTruncated: false,
213
+ Contents: [
214
+ {
215
+ Key: s3Key,
216
+ LastModified: moment().toISOString(),
217
+ ETag: 'example-md5',
218
+ Size: 1024
219
+ }
220
+ ]
178
221
  })
179
222
 
180
223
  await refreshS3Metadata()
@@ -186,33 +229,29 @@ describe('s3 operations', () => {
186
229
  it('skips file processing if a file is older than one week', async () => {
187
230
  const s3Key1 = `${moment().format('YYYY-MM-DD')}/test1.xml`
188
231
 
189
- AwsMock.S3.mockedMethods.listObjectsV2
232
+ s3.send
190
233
  .mockReturnValue({
191
- promise: () => ({
192
- IsTruncated: false,
193
- Contents: [
194
- {
195
- Key: s3Key1,
196
- LastModified: moment().subtract(1, 'days').toISOString(),
197
- ETag: 'example-md5',
198
- Size: 1024
199
- }
200
- ]
201
- })
234
+ IsTruncated: false,
235
+ Contents: [
236
+ {
237
+ Key: s3Key1,
238
+ LastModified: moment().subtract(1, 'days').toISOString(),
239
+ ETag: 'example-md5',
240
+ Size: 1024
241
+ }
242
+ ]
202
243
  })
203
244
  .mockReturnValueOnce({
204
- promise: () => ({
205
- IsTruncated: true,
206
- NextContinuationToken: 'token',
207
- Contents: [
208
- {
209
- Key: s3Key1,
210
- LastModified: moment().subtract(1, 'days').toISOString(),
211
- ETag: 'example-md5',
212
- Size: 1024
213
- }
214
- ]
215
- })
245
+ IsTruncated: true,
246
+ NextContinuationToken: 'token',
247
+ Contents: [
248
+ {
249
+ Key: s3Key1,
250
+ LastModified: moment().subtract(1, 'days').toISOString(),
251
+ ETag: 'example-md5',
252
+ Size: 1024
253
+ }
254
+ ]
216
255
  })
217
256
 
218
257
  await refreshS3Metadata()
@@ -221,15 +260,11 @@ describe('s3 operations', () => {
221
260
  expect(salesApi.upsertTransactionFile).not.toHaveBeenCalled()
222
261
  })
223
262
 
224
- it('logs any errors raised by calling s3.listObjectsV2', async () => {
263
+ it('logs any errors raised by calling ListObjectsV2Command', async () => {
225
264
  const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
226
265
 
227
266
  const testError = new Error('Test error')
228
- AwsMock.S3.mockedMethods.listObjectsV2.mockReturnValue({
229
- promise: () => {
230
- throw testError
231
- }
232
- })
267
+ s3.send.mockRejectedValueOnce(testError)
233
268
 
234
269
  await expect(refreshS3Metadata()).rejects.toThrow(testError)
235
270
  expect(consoleErrorSpy).toHaveBeenCalledWith(testError)
@@ -238,10 +273,8 @@ describe('s3 operations', () => {
238
273
  it('raises a warning if the bucket is empty', async () => {
239
274
  const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
240
275
 
241
- AwsMock.S3.mockedMethods.listObjectsV2.mockReturnValueOnce({
242
- promise: () => ({
243
- IsTruncated: false
244
- })
276
+ s3.send.mockReturnValueOnce({
277
+ IsTruncated: false
245
278
  })
246
279
 
247
280
  await refreshS3Metadata()
package/src/io/db.js CHANGED
@@ -10,13 +10,11 @@ const { docClient } = AWS()
10
10
  * @returns {Promise<void>}
11
11
  */
12
12
  export const updateFileStagingTable = async ({ filename, ...entries }) => {
13
- await docClient
14
- .update({
15
- TableName: config.db.fileStagingTable,
16
- Key: { filename },
17
- ...docClient.createUpdateExpression({ expires: Math.floor(Date.now() / 1000) + config.db.stagingTtlDelta, ...entries })
18
- })
19
- .promise()
13
+ await docClient.update({
14
+ TableName: config.db.fileStagingTable,
15
+ Key: { filename },
16
+ ...docClient.createUpdateExpression({ expires: Math.floor(Date.now() / 1000) + config.db.stagingTtlDelta, ...entries })
17
+ })
20
18
  }
21
19
 
22
20
  /**
@@ -42,7 +40,7 @@ export const getFileRecords = async (...stages) => {
42
40
  * @returns {DocumentClient.AttributeMap}
43
41
  */
44
42
  export const getFileRecord = async filename => {
45
- const result = await docClient.get({ TableName: config.db.fileStagingTable, Key: { filename }, ConsistentRead: true }).promise()
43
+ const result = await docClient.get({ TableName: config.db.fileStagingTable, Key: { filename }, ConsistentRead: true })
46
44
  return result.Item
47
45
  }
48
46
 
package/src/io/s3.js CHANGED
@@ -4,11 +4,12 @@ import config from '../config.js'
4
4
  import { DYNAMICS_IMPORT_STAGE } from '../staging/constants.js'
5
5
  import { storeS3Metadata } from '../transport/storeS3MetaData.js'
6
6
  import { AWS, salesApi } from '@defra-fish/connectors-lib'
7
- const { s3 } = AWS()
7
+ const { s3, ListObjectsV2Command } = AWS()
8
8
 
9
9
  const listObjectsV2 = async function (params) {
10
10
  try {
11
- return s3.listObjectsV2(params).promise()
11
+ const command = new ListObjectsV2Command(params)
12
+ return await s3.send(command)
12
13
  } catch (e) {
13
14
  console.error(e)
14
15
  throw e
@@ -5,12 +5,12 @@ import { stage } from '../pocl-data-staging.js'
5
5
  import { createTransactions } from '../create-transactions.js'
6
6
  import { finaliseTransactions } from '../finalise-transactions.js'
7
7
  import { getFileRecord, updateFileStagingTable } from '../../io/db.js'
8
+
8
9
  import fs from 'fs'
9
10
 
10
11
  jest.mock('../create-transactions.js')
11
12
  jest.mock('../finalise-transactions.js')
12
13
  jest.mock('../../io/db.js')
13
- jest.mock('fs')
14
14
  jest.mock('md5-file', () => () => 'test-md5')
15
15
 
16
16
  jest.mock('@defra-fish/connectors-lib', () => {
@@ -37,6 +37,7 @@ describe('pocl data staging', () => {
37
37
  ['the file has not previously been processed', undefined],
38
38
  ['the file has pending state', { stage: FILE_STAGE.Pending }]
39
39
  ])('%s', async (desc, val) => {
40
+ jest.spyOn(fs, 'statSync')
40
41
  getFileRecord.mockResolvedValueOnce(val)
41
42
  getFileRecord.mockResolvedValueOnce({
42
43
  stagingSucceeded: 5,
@@ -87,6 +88,7 @@ describe('pocl data staging', () => {
87
88
  })
88
89
 
89
90
  it('only runs finalisation if the creation phase has previously been completed', async () => {
91
+ jest.spyOn(fs, 'statSync')
90
92
  getFileRecord.mockResolvedValue({ stage: FILE_STAGE.Finalising })
91
93
  fs.statSync.mockReturnValueOnce({ size: 1024 })
92
94
  salesApi.getTransactionFile.mockResolvedValueOnce({ status: { description: DYNAMICS_IMPORT_STAGE.InProgress } })
@@ -104,6 +106,7 @@ describe('pocl data staging', () => {
104
106
  })
105
107
 
106
108
  it('updates the status in Dynamics if the Dynamics returns InProgress but now marked completed in DynamoDB', async () => {
109
+ jest.spyOn(fs, 'statSync')
107
110
  salesApi.getTransactionFile.mockResolvedValueOnce({ status: { description: DYNAMICS_IMPORT_STAGE.InProgress } })
108
111
  getFileRecord.mockResolvedValue({ stage: FILE_STAGE.Completed })
109
112
  fs.statSync.mockReturnValueOnce({ size: 1024 })
@@ -116,6 +119,7 @@ describe('pocl data staging', () => {
116
119
  })
117
120
 
118
121
  it('is a no-op if the file is marked as processed in Dynamics', async () => {
122
+ jest.spyOn(fs, 'statSync')
119
123
  const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
120
124
  salesApi.getTransactionFile.mockResolvedValueOnce({ status: { description: DYNAMICS_IMPORT_STAGE.ProcessedWithWarnings } })
121
125
  getFileRecord.mockResolvedValue({ stage: FILE_STAGE.Completed })
@@ -1,9 +1,10 @@
1
1
  import { s3ToLocal } from '../s3-to-local.js'
2
2
  import stream from 'stream'
3
- import AwsMock from 'aws-sdk'
3
+ import { AWS } from '@defra-fish/connectors-lib'
4
+ import fs from 'fs'
5
+ const { s3 } = AWS.mock.results[0].value
4
6
 
5
7
  const MOCK_TMP = '/tmp/local/mock'
6
- jest.mock('fs')
7
8
  jest.mock('stream')
8
9
  jest.mock('../../io/file.js', () => ({
9
10
  getTempDir: jest.fn((...subfolders) => `${MOCK_TMP}/${subfolders.join('/')}`)
@@ -15,17 +16,23 @@ jest.mock('../../config.js', () => ({
15
16
  }
16
17
  }))
17
18
 
19
+ jest.mock('@defra-fish/connectors-lib', () => ({
20
+ AWS: jest.fn(() => ({
21
+ s3: {
22
+ getObject: jest.fn(() => ({
23
+ createReadStream: jest.fn(() => ({}))
24
+ }))
25
+ }
26
+ }))
27
+ }))
28
+
18
29
  describe('s3-to-local', () => {
19
30
  beforeEach(() => {
20
31
  jest.clearAllMocks()
21
- AwsMock.__resetAll()
22
32
  })
23
33
 
24
34
  it('retrieves a file from s3 for a given key', async () => {
25
- const mockCreateReadStream = jest.fn()
26
- AwsMock.S3.mockedMethods.getObject.mockImplementationOnce(() => {
27
- return { createReadStream: mockCreateReadStream }
28
- })
35
+ jest.spyOn(fs, 'createWriteStream').mockReturnValueOnce({})
29
36
  stream.pipeline.mockImplementation(
30
37
  jest.fn((streams, callback) => {
31
38
  callback()
@@ -33,10 +40,12 @@ describe('s3-to-local', () => {
33
40
  )
34
41
 
35
42
  const result = await s3ToLocal('/example/testS3Key.xml')
43
+ const { createReadStream } = s3.getObject.mock.results[0].value
44
+
36
45
  expect(result).toBe(`${MOCK_TMP}/example/testS3Key.xml`)
37
- expect(mockCreateReadStream).toHaveBeenCalled()
46
+ expect(createReadStream).toHaveBeenCalled()
38
47
  expect(stream.pipeline).toHaveBeenCalled()
39
- expect(AwsMock.S3.mockedMethods.getObject).toHaveBeenCalledWith({
48
+ expect(s3.getObject).toHaveBeenCalledWith({
40
49
  Bucket: 'testbucket',
41
50
  Key: '/example/testS3Key.xml'
42
51
  })