@cumulus/async-operations 15.0.4 → 16.0.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.
@@ -32,8 +32,6 @@ const {
32
32
  startAsyncOperation,
33
33
  } = require('../dist/async_operations');
34
34
 
35
- const dynamoTableName = 'notUsedDynamoTableName';
36
-
37
35
  let stubbedEcsRunTaskParams;
38
36
  let stubbedEcsRunTaskResult;
39
37
  let ecsClient;
@@ -79,7 +77,6 @@ test.before(async (t) => {
79
77
  Environment: {
80
78
  Variables: {
81
79
  ES_HOST: 'es-host',
82
- AsyncOperationsTable: 'async-operations-table',
83
80
  },
84
81
  },
85
82
  };
@@ -88,14 +85,6 @@ test.before(async (t) => {
88
85
  promise: () => Promise.resolve(t.context.functionConfig),
89
86
  });
90
87
 
91
- t.context.createSpy = sinon.spy((record) => Promise.resolve(record));
92
- t.context.deleteSpy = sinon.spy(() => true);
93
- t.context.stubbedAsyncOperationsModel = class {
94
- create = t.context.createSpy;
95
-
96
- delete = t.context.deleteSpy;
97
- };
98
-
99
88
  t.context.asyncOperationPgModel = new AsyncOperationPgModel();
100
89
  });
101
90
 
@@ -112,10 +101,6 @@ test.beforeEach((t) => {
112
101
  };
113
102
  });
114
103
 
115
- test.afterEach.always((t) => {
116
- t.context.createSpy.resetHistory();
117
- });
118
-
119
104
  test.after.always(async (t) => {
120
105
  sinon.restore();
121
106
  await recursivelyDeleteS3Bucket(systemBucket);
@@ -128,11 +113,6 @@ test.after.always(async (t) => {
128
113
  });
129
114
 
130
115
  test.serial('startAsyncOperation uploads the payload to S3', async (t) => {
131
- const createSpy = sinon.spy((obj) => obj);
132
- const stubbedAsyncOperationsModel = class {
133
- create = createSpy;
134
- };
135
-
136
116
  stubbedEcsRunTaskResult = {
137
117
  tasks: [{ taskArn: randomString() }],
138
118
  failures: [],
@@ -149,21 +129,15 @@ test.serial('startAsyncOperation uploads the payload to S3', async (t) => {
149
129
  operationType: 'ES Index',
150
130
  payload,
151
131
  stackName,
152
- dynamoTableName: dynamoTableName,
153
132
  knexConfig: knexConfig,
154
133
  systemBucket,
155
- }, stubbedAsyncOperationsModel);
134
+ });
156
135
 
157
136
  const payloadObjectData = await getJsonS3Object(systemBucket, `${stackName}/async-operation-payloads/${id}.json`);
158
137
  t.deepEqual(payloadObjectData, payload);
159
138
  });
160
139
 
161
140
  test.serial('The AsyncOperation start method starts an ECS task with the correct parameters', async (t) => {
162
- const createSpy = sinon.spy((obj) => obj);
163
- const stubbedAsyncOperationsModel = class {
164
- create = createSpy;
165
- };
166
-
167
141
  stubbedEcsRunTaskParams = {};
168
142
  stubbedEcsRunTaskResult = {
169
143
  tasks: [{ taskArn: randomString() }],
@@ -186,11 +160,10 @@ test.serial('The AsyncOperation start method starts an ECS task with the correct
186
160
  operationType: 'ES Index',
187
161
  payload,
188
162
  stackName,
189
- dynamoTableName: dynamoTableName,
190
163
  knexConfig: knexConfig,
191
164
  systemBucket,
192
165
  useLambdaEnvironmentVariables: true,
193
- }, stubbedAsyncOperationsModel);
166
+ });
194
167
 
195
168
  t.is(stubbedEcsRunTaskParams.cluster, cluster);
196
169
  t.is(stubbedEcsRunTaskParams.taskDefinition, asyncOperationTaskDefinition);
@@ -202,17 +175,11 @@ test.serial('The AsyncOperation start method starts an ECS task with the correct
202
175
  });
203
176
 
204
177
  t.is(environmentOverrides.asyncOperationId, id);
205
- t.is(environmentOverrides.asyncOperationsTable, dynamoTableName);
206
178
  t.is(environmentOverrides.lambdaName, lambdaName);
207
179
  t.is(environmentOverrides.payloadUrl, `s3://${systemBucket}/${stackName}/async-operation-payloads/${id}.json`);
208
180
  });
209
181
 
210
182
  test.serial('The AsyncOperation start method starts an ECS task with the asyncOperationId passed in', async (t) => {
211
- const createSpy = sinon.spy((obj) => obj);
212
- const stubbedAsyncOperationsModel = class {
213
- create = createSpy;
214
- };
215
-
216
183
  stubbedEcsRunTaskParams = {};
217
184
  stubbedEcsRunTaskResult = {
218
185
  tasks: [{ taskArn: randomString() }],
@@ -237,11 +204,10 @@ test.serial('The AsyncOperation start method starts an ECS task with the asyncOp
237
204
  operationType: 'ES Index',
238
205
  payload,
239
206
  stackName,
240
- dynamoTableName: dynamoTableName,
241
207
  knexConfig: knexConfig,
242
208
  systemBucket,
243
209
  useLambdaEnvironmentVariables: true,
244
- }, stubbedAsyncOperationsModel);
210
+ });
245
211
 
246
212
  t.is(stubbedEcsRunTaskParams.cluster, cluster);
247
213
  t.is(stubbedEcsRunTaskParams.taskDefinition, asyncOperationTaskDefinition);
@@ -254,26 +220,19 @@ test.serial('The AsyncOperation start method starts an ECS task with the asyncOp
254
220
 
255
221
  t.is(id, asyncOperationId);
256
222
  t.is(environmentOverrides.asyncOperationId, asyncOperationId);
257
- t.is(environmentOverrides.asyncOperationsTable, dynamoTableName);
258
223
  t.is(environmentOverrides.lambdaName, lambdaName);
259
224
  t.is(environmentOverrides.payloadUrl, `s3://${systemBucket}/${stackName}/async-operation-payloads/${asyncOperationId}.json`);
260
225
  });
261
226
 
262
- test.serial('The startAsyncOperation method throws error and calls database model create method '
263
- + 'when it is unable to create an ECS task', async (t) => {
264
- const createSpy = sinon.spy((obj) => obj);
265
- const stubbedAsyncOperationsModel = class {
266
- create = createSpy;
267
- };
268
-
227
+ test.serial('The startAsyncOperation method throws error and calls createAsyncOperation when unable to start ECS task', async (t) => {
269
228
  stubbedEcsRunTaskResult = {
270
229
  tasks: [],
271
230
  failures: [{ arn: randomString(), reason: 'out of cheese' }],
272
231
  };
273
- const stackName = randomString();
274
232
 
233
+ const asyncOperationId = uuidv4();
275
234
  const asyncOperationParams = {
276
- asyncOperationId: uuidv4(),
235
+ asyncOperationId,
277
236
  asyncOperationTaskDefinition: randomString(),
278
237
  cluster: randomString(),
279
238
  callerLambdaName: randomString(),
@@ -281,8 +240,7 @@ test.serial('The startAsyncOperation method throws error and calls database mode
281
240
  description: randomString(),
282
241
  operationType: 'ES Index',
283
242
  payload: {},
284
- stackName,
285
- dynamoTableName: dynamoTableName,
243
+ stackName: randomString(),
286
244
  knexConfig: knexConfig,
287
245
  systemBucket,
288
246
  };
@@ -291,71 +249,31 @@ test.serial('The startAsyncOperation method throws error and calls database mode
291
249
  message: 'Failed to start AsyncOperation: out of cheese',
292
250
  };
293
251
  await t.throwsAsync(
294
- startAsyncOperation(asyncOperationParams, stubbedAsyncOperationsModel),
252
+ startAsyncOperation(asyncOperationParams),
295
253
  expectedErrorThrown
296
254
  );
297
255
 
298
- const spyCall = createSpy.getCall(0).args[0];
256
+ const asyncOperationPgRecord = await t.context.asyncOperationPgModel.get(
257
+ t.context.testKnex,
258
+ { id: asyncOperationId }
259
+ );
299
260
 
300
261
  const expected = {
301
262
  id: asyncOperationParams.asyncOperationId,
302
263
  description: asyncOperationParams.description,
303
264
  operationType: asyncOperationParams.operationType,
304
265
  status: 'RUNNER_FAILED',
266
+ task_arn: null,
305
267
  };
306
268
 
307
- t.like(spyCall, expected);
308
- t.deepEqual(omit(spyCall, ['createdAt', 'updatedAt', 'output']), expected);
309
- t.is(spyCall.id, asyncOperationParams.asyncOperationId);
310
- const output = JSON.parse(spyCall.output || {});
311
- t.like(output, { name: 'EcsStartTaskError', message: expectedErrorThrown.message });
312
- });
313
-
314
- test('startAsyncOperation calls Dynamo model create method', async (t) => {
315
- const stackName = randomString();
316
- const description = randomString();
317
- const taskArn = randomString();
318
-
319
- stubbedEcsRunTaskResult = {
320
- tasks: [{ taskArn }],
321
- failures: [],
322
- };
323
- const result = await startAsyncOperation({
324
- asyncOperationTaskDefinition: randomString(),
325
- cluster: randomString(),
326
- callerLambdaName: randomString(),
327
- lambdaName: randomString(),
328
- description,
329
- operationType: 'ES Index',
330
- payload: {},
331
- stackName,
332
- dynamoTableName: dynamoTableName,
333
- knexConfig: knexConfig,
334
- systemBucket,
335
- }, t.context.stubbedAsyncOperationsModel);
336
-
337
- const spyCall = t.context.createSpy.getCall(0).args[0];
338
-
339
- const expected = {
340
- description,
341
- operationType: 'ES Index',
342
- status: 'RUNNING',
343
- taskArn,
344
- };
345
-
346
- t.like(result, {
347
- ...expected,
348
- id: spyCall.id,
349
- });
350
- t.deepEqual(omit(spyCall, ['id', 'createdAt', 'updatedAt']), expected);
351
- t.truthy(spyCall.id);
269
+ const omitList = ['created_at', 'updated_at', 'cumulus_id', 'output'];
270
+ t.deepEqual(
271
+ omit(asyncOperationPgRecord, omitList),
272
+ translateApiAsyncOperationToPostgresAsyncOperation(omit(expected, omitList))
273
+ );
352
274
  });
353
275
 
354
- test.serial('The startAsyncOperation writes records to all data stores', async (t) => {
355
- const createSpy = sinon.spy((createObject) => createObject);
356
- const stubbedAsyncOperationsModel = class {
357
- create = createSpy;
358
- };
276
+ test('The startAsyncOperation writes records to all data stores', async (t) => {
359
277
  const description = randomString();
360
278
  const stackName = randomString();
361
279
  const operationType = 'ES Index';
@@ -375,12 +293,10 @@ test.serial('The startAsyncOperation writes records to all data stores', async (
375
293
  operationType,
376
294
  payload: {},
377
295
  stackName,
378
- dynamoTableName: dynamoTableName,
379
296
  knexConfig: knexConfig,
380
297
  systemBucket,
381
- }, stubbedAsyncOperationsModel);
298
+ });
382
299
 
383
- const asyncOpDynamoSpyRecord = createSpy.getCall(0).args[0];
384
300
  const asyncOperationPgRecord = await t.context.asyncOperationPgModel.get(
385
301
  t.context.testKnex,
386
302
  { id }
@@ -401,19 +317,16 @@ test.serial('The startAsyncOperation writes records to all data stores', async (
401
317
  t.deepEqual(
402
318
  await t.context.esAsyncOperationsClient.get(id),
403
319
  {
404
- ...asyncOpDynamoSpyRecord,
320
+ ...expected,
405
321
  _id: esRecord._id,
406
322
  timestamp: esRecord.timestamp,
323
+ updatedAt: esRecord.updatedAt,
324
+ createdAt: esRecord.createdAt,
407
325
  }
408
326
  );
409
- t.deepEqual(omit(asyncOpDynamoSpyRecord, ['createdAt', 'updatedAt']), omit(expected, ['createdAt', 'updatedAt']));
410
327
  });
411
328
 
412
329
  test.serial('The startAsyncOperation writes records with correct timestamps', async (t) => {
413
- const createSpy = sinon.spy((createObject) => createObject);
414
- const stubbedAsyncOperationsModel = class {
415
- create = createSpy;
416
- };
417
330
  const description = randomString();
418
331
  const stackName = randomString();
419
332
  const operationType = 'ES Index';
@@ -433,30 +346,21 @@ test.serial('The startAsyncOperation writes records with correct timestamps', as
433
346
  operationType,
434
347
  payload: {},
435
348
  stackName,
436
- dynamoTableName: dynamoTableName,
437
349
  knexConfig: knexConfig,
438
350
  systemBucket,
439
- }, stubbedAsyncOperationsModel);
351
+ });
440
352
 
441
- const asyncOpDynamoSpyRecord = createSpy.getCall(0).args[0];
442
353
  const asyncOperationPgRecord = await t.context.asyncOperationPgModel.get(
443
354
  t.context.testKnex,
444
355
  { id }
445
356
  );
446
- t.is(asyncOperationPgRecord.created_at.getTime(), asyncOpDynamoSpyRecord.createdAt);
447
- t.is(asyncOperationPgRecord.updated_at.getTime(), asyncOpDynamoSpyRecord.updatedAt);
448
357
 
449
358
  const esRecord = await t.context.esAsyncOperationsClient.get(id);
450
- t.is(esRecord.createdAt, asyncOpDynamoSpyRecord.createdAt);
451
- t.is(esRecord.updatedAt, asyncOpDynamoSpyRecord.updatedAt);
359
+ t.is(asyncOperationPgRecord.created_at.getTime(), esRecord.createdAt);
360
+ t.is(asyncOperationPgRecord.updated_at.getTime(), esRecord.updatedAt);
452
361
  });
453
362
 
454
363
  test.serial('The startAsyncOperation method returns the newly-generated record', async (t) => {
455
- const createSpy = sinon.spy((obj) => obj);
456
- const stubbedAsyncOperationsModel = class {
457
- create = createSpy;
458
- };
459
-
460
364
  const taskArn = randomString();
461
365
  stubbedEcsRunTaskResult = {
462
366
  tasks: [{ taskArn }],
@@ -474,19 +378,14 @@ test.serial('The startAsyncOperation method returns the newly-generated record',
474
378
  operationType: 'ES Index',
475
379
  payload: {},
476
380
  stackName,
477
- dynamoTableName: dynamoTableName,
478
381
  knexConfig: knexConfig,
479
382
  systemBucket,
480
- }, stubbedAsyncOperationsModel);
383
+ });
481
384
 
482
385
  t.is(results.taskArn, taskArn);
483
386
  });
484
387
 
485
388
  test.serial('The startAsyncOperation method throws error if callerLambdaName parameter is missing', async (t) => {
486
- const stubbedAsyncOperationsModel = class {
487
- create = sinon.stub();
488
- };
489
-
490
389
  stubbedEcsRunTaskParams = {};
491
390
  stubbedEcsRunTaskResult = {
492
391
  tasks: [{ taskArn: randomString() }],
@@ -502,11 +401,10 @@ test.serial('The startAsyncOperation method throws error if callerLambdaName par
502
401
  operationType: 'ES Index',
503
402
  payload: { x: randomString() },
504
403
  stackName: randomString,
505
- dynamoTableName: dynamoTableName,
506
404
  knexConfig: knexConfig,
507
405
  systemBucket,
508
406
  useLambdaEnvironmentVariables: true,
509
- }, stubbedAsyncOperationsModel),
407
+ }),
510
408
  { instanceOf: MissingRequiredArgument }
511
409
  );
512
410
  });
@@ -521,16 +419,10 @@ test('getLambdaEnvironmentVariables returns expected environment variables', (t)
521
419
 
522
420
  t.deepEqual(new Set(vars), new Set([
523
421
  { name: 'ES_HOST', value: 'es-host' },
524
- { name: 'AsyncOperationsTable', value: 'async-operations-table' },
525
422
  ]));
526
423
  });
527
424
 
528
425
  test.serial('ECS task params contain lambda environment variables when useLambdaEnvironmentVariables is set to true', async (t) => {
529
- const createSpy = sinon.spy((obj) => obj);
530
- const stubbedAsyncOperationsModel = class {
531
- create = createSpy;
532
- };
533
-
534
426
  stubbedEcsRunTaskResult = {
535
427
  tasks: [{ taskArn: randomString() }],
536
428
  failures: [],
@@ -548,10 +440,9 @@ test.serial('ECS task params contain lambda environment variables when useLambda
548
440
  payload: {},
549
441
  useLambdaEnvironmentVariables: true,
550
442
  stackName,
551
- dynamoTableName: dynamoTableName,
552
443
  knexConfig: knexConfig,
553
444
  systemBucket,
554
- }, stubbedAsyncOperationsModel);
445
+ });
555
446
 
556
447
  const environmentOverrides = {};
557
448
  stubbedEcsRunTaskParams.overrides.containerOverrides[0].environment.forEach((env) => {
@@ -559,11 +450,10 @@ test.serial('ECS task params contain lambda environment variables when useLambda
559
450
  });
560
451
 
561
452
  t.is(environmentOverrides.ES_HOST, 'es-host');
562
- t.is(environmentOverrides.AsyncOperationsTable, 'async-operations-table');
563
453
  });
564
454
 
565
- test.serial('createAsyncOperation() does not write to Elasticsearch/DynamoDB if writing to PostgreSQL fails', async (t) => {
566
- const { id, createObject } = t.context;
455
+ test.serial('createAsyncOperation throws if stackName is not provided', async (t) => {
456
+ const { createObject } = t.context;
567
457
 
568
458
  const fakeAsyncOpPgModel = {
569
459
  create: () => {
@@ -575,51 +465,56 @@ test.serial('createAsyncOperation() does not write to Elasticsearch/DynamoDB if
575
465
  knex: t.context.testKnex,
576
466
  asyncOperationPgModel: fakeAsyncOpPgModel,
577
467
  createObject,
468
+ systemBucket: 'FakeBucket',
578
469
  };
579
470
  await t.throwsAsync(
580
- createAsyncOperation(createParams, t.context.stubbedAsyncOperationsModel),
581
- { message: 'something bad' }
471
+ createAsyncOperation(createParams),
472
+ { name: 'TypeError' }
582
473
  );
474
+ });
583
475
 
584
- t.false(t.context.createSpy.called);
585
- const dbRecords = await t.context.asyncOperationPgModel
586
- .search(t.context.testKnex, { id });
587
- t.is(dbRecords.length, 0);
588
- t.false(await t.context.esAsyncOperationsClient.exists(
589
- id
590
- ));
476
+ test('createAsyncOperation throws if systemBucket is not provided', async (t) => {
477
+ const { createObject } = t.context;
478
+
479
+ const fakeAsyncOpPgModel = {
480
+ create: () => {
481
+ throw new Error('something bad');
482
+ },
483
+ };
484
+
485
+ const createParams = {
486
+ knex: t.context.testKnex,
487
+ asyncOperationPgModel: fakeAsyncOpPgModel,
488
+ createObject,
489
+ stackName: 'fakeStack',
490
+ };
491
+ await t.throwsAsync(
492
+ createAsyncOperation(createParams),
493
+ { name: 'TypeError' }
494
+ );
591
495
  });
592
496
 
593
- test.serial('createAsyncOperation() does not write to Elasticsearch/PostgreSQL if writing to DynamoDB fails', async (t) => {
497
+ test.serial('createAsyncOperation() does not write to Elasticsearch if writing to PostgreSQL fails', async (t) => {
594
498
  const { id, createObject } = t.context;
595
499
 
596
- const fakeCreate = () => {
597
- throw new Error('something bad');
500
+ const fakeAsyncOpPgModel = {
501
+ create: () => {
502
+ throw new Error('something bad');
503
+ },
598
504
  };
599
- const fakeCreateSpy = sinon.spy(fakeCreate);
600
- const deleteSpy = sinon.spy();
601
- class fakeAsyncOperationsModel {
602
- create(record) {
603
- return fakeCreateSpy(record);
604
- }
605
-
606
- delete(record) {
607
- deleteSpy(record);
608
- }
609
- }
610
505
 
611
506
  const createParams = {
612
507
  knex: t.context.testKnex,
508
+ asyncOperationPgModel: fakeAsyncOpPgModel,
613
509
  createObject,
510
+ stackName: 'FakeStack',
511
+ systemBucket: 'FakeBucket',
614
512
  };
615
513
  await t.throwsAsync(
616
- createAsyncOperation(createParams, fakeAsyncOperationsModel),
514
+ createAsyncOperation(createParams),
617
515
  { message: 'something bad' }
618
516
  );
619
517
 
620
- t.true(fakeCreateSpy.threw());
621
- // Not called because no record was ever created
622
- t.false(deleteSpy.called);
623
518
  const dbRecords = await t.context.asyncOperationPgModel
624
519
  .search(t.context.testKnex, { id });
625
520
  t.is(dbRecords.length, 0);
@@ -628,7 +523,7 @@ test.serial('createAsyncOperation() does not write to Elasticsearch/PostgreSQL i
628
523
  ));
629
524
  });
630
525
 
631
- test.serial('createAsyncOperation() does not write to DynamoDB/PostgreSQL if writing to Elasticsearch fails', async (t) => {
526
+ test.serial('createAsyncOperation() does not write to PostgreSQL if writing to Elasticsearch fails', async (t) => {
632
527
  const { id, createObject } = t.context;
633
528
  const fakeEsClient = {
634
529
  index: () => {
@@ -640,14 +535,14 @@ test.serial('createAsyncOperation() does not write to DynamoDB/PostgreSQL if wri
640
535
  knex: t.context.testKnex,
641
536
  createObject,
642
537
  esClient: fakeEsClient,
538
+ stackName: 'FakeStack',
539
+ systemBucket: 'FakeBucket',
643
540
  };
644
541
  await t.throwsAsync(
645
- createAsyncOperation(createParams, t.context.stubbedAsyncOperationsModel),
542
+ createAsyncOperation(createParams),
646
543
  { message: 'ES something bad' }
647
544
  );
648
545
 
649
- t.true(t.context.createSpy.called);
650
- t.true(t.context.deleteSpy.calledWith({ id: createObject.id }));
651
546
  const dbRecords = await t.context.asyncOperationPgModel
652
547
  .search(t.context.testKnex, { id });
653
548
  t.is(dbRecords.length, 0);