@friggframework/devtools 2.0.0--canary.606.7d3473f.0 → 2.0.0--canary.608.ba60ba6.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.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Tests for Integration Builder
3
- *
3
+ *
4
4
  * Tests integration-specific Lambda functions and SQS queues
5
5
  */
6
6
 
@@ -17,9 +17,7 @@ describe('IntegrationBuilder', () => {
17
17
  describe('shouldExecute()', () => {
18
18
  it('should return true when integrations array has items', () => {
19
19
  const appDefinition = {
20
- integrations: [
21
- { Definition: { name: 'test' } },
22
- ],
20
+ integrations: [{ Definition: { name: 'test' } }],
23
21
  };
24
22
 
25
23
  expect(integrationBuilder.shouldExecute(appDefinition)).toBe(true);
@@ -85,9 +83,7 @@ describe('IntegrationBuilder', () => {
85
83
 
86
84
  it('should error when integration is missing Definition', () => {
87
85
  const appDefinition = {
88
- integrations: [
89
- { someOtherField: 'value' },
90
- ],
86
+ integrations: [{ someOtherField: 'value' }],
91
87
  };
92
88
 
93
89
  const result = integrationBuilder.validate(appDefinition);
@@ -100,9 +96,7 @@ describe('IntegrationBuilder', () => {
100
96
 
101
97
  it('should error when integration Definition is missing name', () => {
102
98
  const appDefinition = {
103
- integrations: [
104
- { Definition: {} },
105
- ],
99
+ integrations: [{ Definition: {} }],
106
100
  };
107
101
 
108
102
  const result = integrationBuilder.validate(appDefinition);
@@ -132,9 +126,7 @@ describe('IntegrationBuilder', () => {
132
126
  describe('build()', () => {
133
127
  it('should create HTTP handler for integration', async () => {
134
128
  const appDefinition = {
135
- integrations: [
136
- { Definition: { name: 'hubspot' } },
137
- ],
129
+ integrations: [{ Definition: { name: 'hubspot' } }],
138
130
  };
139
131
 
140
132
  const result = await integrationBuilder.build(appDefinition, {});
@@ -147,9 +139,7 @@ describe('IntegrationBuilder', () => {
147
139
 
148
140
  it('should configure HTTP API event for integration', async () => {
149
141
  const appDefinition = {
150
- integrations: [
151
- { Definition: { name: 'salesforce' } },
152
- ],
142
+ integrations: [{ Definition: { name: 'salesforce' } }],
153
143
  };
154
144
 
155
145
  const result = await integrationBuilder.build(appDefinition, {});
@@ -166,9 +156,7 @@ describe('IntegrationBuilder', () => {
166
156
 
167
157
  it('should create SQS queue for integration', async () => {
168
158
  const appDefinition = {
169
- integrations: [
170
- { Definition: { name: 'slack' } },
171
- ],
159
+ integrations: [{ Definition: { name: 'slack' } }],
172
160
  };
173
161
 
174
162
  const result = await integrationBuilder.build(appDefinition, {});
@@ -179,39 +167,39 @@ describe('IntegrationBuilder', () => {
179
167
 
180
168
  it('should configure queue with correct retention and visibility timeout', async () => {
181
169
  const appDefinition = {
182
- integrations: [
183
- { Definition: { name: 'test' } },
184
- ],
170
+ integrations: [{ Definition: { name: 'test' } }],
185
171
  };
186
172
 
187
173
  const result = await integrationBuilder.build(appDefinition, {});
188
174
 
189
- expect(result.resources.TestQueue.Properties.MessageRetentionPeriod).toBe(345600);
190
- expect(result.resources.TestQueue.Properties.VisibilityTimeout).toBe(1800);
175
+ expect(
176
+ result.resources.TestQueue.Properties.MessageRetentionPeriod
177
+ ).toBe(345600);
178
+ expect(
179
+ result.resources.TestQueue.Properties.VisibilityTimeout
180
+ ).toBe(1800);
191
181
  });
192
182
 
193
183
  it('should configure redrive policy to internal error queue', async () => {
194
184
  const appDefinition = {
195
- integrations: [
196
- { Definition: { name: 'test' } },
197
- ],
185
+ integrations: [{ Definition: { name: 'test' } }],
198
186
  };
199
187
 
200
188
  const result = await integrationBuilder.build(appDefinition, {});
201
189
 
202
- expect(result.resources.TestQueue.Properties.RedrivePolicy).toEqual({
203
- maxReceiveCount: 3,
204
- deadLetterTargetArn: {
205
- 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
206
- },
207
- });
190
+ expect(result.resources.TestQueue.Properties.RedrivePolicy).toEqual(
191
+ {
192
+ maxReceiveCount: 3,
193
+ deadLetterTargetArn: {
194
+ 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
195
+ },
196
+ }
197
+ );
208
198
  });
209
199
 
210
200
  it('should create queue worker function', async () => {
211
201
  const appDefinition = {
212
- integrations: [
213
- { Definition: { name: 'hubspot' } },
214
- ],
202
+ integrations: [{ Definition: { name: 'hubspot' } }],
215
203
  };
216
204
 
217
205
  const result = await integrationBuilder.build(appDefinition, {});
@@ -221,9 +209,7 @@ describe('IntegrationBuilder', () => {
221
209
 
222
210
  it('should configure queue worker with SQS event', async () => {
223
211
  const appDefinition = {
224
- integrations: [
225
- { Definition: { name: 'test' } },
226
- ],
212
+ integrations: [{ Definition: { name: 'test' } }],
227
213
  };
228
214
 
229
215
  const result = await integrationBuilder.build(appDefinition, {});
@@ -241,9 +227,7 @@ describe('IntegrationBuilder', () => {
241
227
 
242
228
  it('should set queue worker timeout to 600 seconds', async () => {
243
229
  const appDefinition = {
244
- integrations: [
245
- { Definition: { name: 'test' } },
246
- ],
230
+ integrations: [{ Definition: { name: 'test' } }],
247
231
  };
248
232
 
249
233
  const result = await integrationBuilder.build(appDefinition, {});
@@ -253,21 +237,19 @@ describe('IntegrationBuilder', () => {
253
237
 
254
238
  it('should set queue worker reserved concurrency', async () => {
255
239
  const appDefinition = {
256
- integrations: [
257
- { Definition: { name: 'test' } },
258
- ],
240
+ integrations: [{ Definition: { name: 'test' } }],
259
241
  };
260
242
 
261
243
  const result = await integrationBuilder.build(appDefinition, {});
262
244
 
263
- expect(result.functions.testQueueWorker.reservedConcurrency).toBe(20);
245
+ expect(result.functions.testQueueWorker.reservedConcurrency).toBe(
246
+ 20
247
+ );
264
248
  });
265
249
 
266
250
  it('should add queue URL to environment variables', async () => {
267
251
  const appDefinition = {
268
- integrations: [
269
- { Definition: { name: 'slack' } },
270
- ],
252
+ integrations: [{ Definition: { name: 'slack' } }],
271
253
  };
272
254
 
273
255
  const result = await integrationBuilder.build(appDefinition, {});
@@ -279,14 +261,14 @@ describe('IntegrationBuilder', () => {
279
261
 
280
262
  it('should add queue name to custom variables', async () => {
281
263
  const appDefinition = {
282
- integrations: [
283
- { Definition: { name: 'stripe' } },
284
- ],
264
+ integrations: [{ Definition: { name: 'stripe' } }],
285
265
  };
286
266
 
287
267
  const result = await integrationBuilder.build(appDefinition, {});
288
268
 
289
- expect(result.custom.StripeQueue).toBe('${self:service}--${self:provider.stage}-StripeQueue');
269
+ expect(result.custom.StripeQueue).toBe(
270
+ '${self:service}--${self:provider.stage}-StripeQueue'
271
+ );
290
272
  });
291
273
 
292
274
  it('should handle multiple integrations', async () => {
@@ -321,9 +303,7 @@ describe('IntegrationBuilder', () => {
321
303
 
322
304
  it('should capitalize integration name for queue reference', async () => {
323
305
  const appDefinition = {
324
- integrations: [
325
- { Definition: { name: 'myIntegration' } },
326
- ],
306
+ integrations: [{ Definition: { name: 'myIntegration' } }],
327
307
  };
328
308
 
329
309
  const result = await integrationBuilder.build(appDefinition, {});
@@ -334,9 +314,7 @@ describe('IntegrationBuilder', () => {
334
314
 
335
315
  it('should handle integration names with hyphens', async () => {
336
316
  const appDefinition = {
337
- integrations: [
338
- { Definition: { name: 'my-integration' } },
339
- ],
317
+ integrations: [{ Definition: { name: 'my-integration' } }],
340
318
  };
341
319
 
342
320
  const result = await integrationBuilder.build(appDefinition, {});
@@ -361,7 +339,8 @@ describe('IntegrationBuilder', () => {
361
339
  };
362
340
 
363
341
  const result = await integrationBuilder.build(appDefinition, {});
364
- const retention = result.resources.TestQueue.Properties.MessageRetentionPeriod;
342
+ const retention =
343
+ result.resources.TestQueue.Properties.MessageRetentionPeriod;
365
344
 
366
345
  // The max SQS DelaySeconds is 900. Retention must comfortably
367
346
  // exceed this to ensure delayed messages are never silently lost.
@@ -377,7 +356,9 @@ describe('IntegrationBuilder', () => {
377
356
  };
378
357
 
379
358
  const result = await integrationBuilder.build(appDefinition, {});
380
- const maxReceiveCount = result.resources.TestQueue.Properties.RedrivePolicy.maxReceiveCount;
359
+ const maxReceiveCount =
360
+ result.resources.TestQueue.Properties.RedrivePolicy
361
+ .maxReceiveCount;
381
362
 
382
363
  // Should allow at least 2 retries (maxReceiveCount >= 3)
383
364
  expect(maxReceiveCount).toBeGreaterThanOrEqual(3);
@@ -393,7 +374,9 @@ describe('IntegrationBuilder', () => {
393
374
  const result = await integrationBuilder.build(appDefinition, {});
394
375
  const sqsEvent = result.functions.testQueueWorker.events[0].sqs;
395
376
 
396
- expect(sqsEvent.functionResponseType).toBe('ReportBatchItemFailures');
377
+ expect(sqsEvent.functionResponseType).toBe(
378
+ 'ReportBatchItemFailures'
379
+ );
397
380
  });
398
381
  });
399
382
 
@@ -406,10 +389,18 @@ describe('IntegrationBuilder', () => {
406
389
  const result = await integrationBuilder.build(appDefinition, {});
407
390
 
408
391
  expect(result.resources.DLQMessageAlarm).toBeDefined();
409
- expect(result.resources.DLQMessageAlarm.Type).toBe('AWS::CloudWatch::Alarm');
410
- expect(result.resources.DLQMessageAlarm.Properties.MetricName).toBe('ApproximateNumberOfMessagesVisible');
411
- expect(result.resources.DLQMessageAlarm.Properties.ComparisonOperator).toBe('GreaterThanThreshold');
412
- expect(result.resources.DLQMessageAlarm.Properties.Threshold).toBe(500);
392
+ expect(result.resources.DLQMessageAlarm.Type).toBe(
393
+ 'AWS::CloudWatch::Alarm'
394
+ );
395
+ expect(result.resources.DLQMessageAlarm.Properties.MetricName).toBe(
396
+ 'ApproximateNumberOfMessagesVisible'
397
+ );
398
+ expect(
399
+ result.resources.DLQMessageAlarm.Properties.ComparisonOperator
400
+ ).toBe('GreaterThanThreshold');
401
+ expect(result.resources.DLQMessageAlarm.Properties.Threshold).toBe(
402
+ 500
403
+ );
413
404
  });
414
405
 
415
406
  it('should wire alarm to InternalErrorBridgeTopic for notifications', async () => {
@@ -419,9 +410,9 @@ describe('IntegrationBuilder', () => {
419
410
 
420
411
  const result = await integrationBuilder.build(appDefinition, {});
421
412
 
422
- expect(result.resources.DLQMessageAlarm.Properties.AlarmActions).toEqual([
423
- { Ref: 'InternalErrorBridgeTopic' },
424
- ]);
413
+ expect(
414
+ result.resources.DLQMessageAlarm.Properties.AlarmActions
415
+ ).toEqual([{ Ref: 'InternalErrorBridgeTopic' }]);
425
416
  });
426
417
 
427
418
  it('should create a DLQ processor Lambda triggered by InternalErrorQueue', async () => {
@@ -435,7 +426,9 @@ describe('IntegrationBuilder', () => {
435
426
  expect(result.functions.dlqProcessor.events[0].sqs.arn).toEqual({
436
427
  'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
437
428
  });
438
- expect(result.functions.dlqProcessor.events[0].sqs.functionResponseType).toBe('ReportBatchItemFailures');
429
+ expect(
430
+ result.functions.dlqProcessor.events[0].sqs.functionResponseType
431
+ ).toBe('ReportBatchItemFailures');
439
432
  });
440
433
 
441
434
  it('DLQ processor should have skipEsbuild, short timeout, and low concurrency', async () => {
@@ -447,7 +440,9 @@ describe('IntegrationBuilder', () => {
447
440
 
448
441
  expect(result.functions.dlqProcessor.skipEsbuild).toBe(true);
449
442
  expect(result.functions.dlqProcessor.package).toBeDefined();
450
- expect(result.functions.dlqProcessor.timeout).toBeLessThanOrEqual(60);
443
+ expect(result.functions.dlqProcessor.timeout).toBeLessThanOrEqual(
444
+ 60
445
+ );
451
446
  expect(result.functions.dlqProcessor.reservedConcurrency).toBe(1);
452
447
  });
453
448
  });
@@ -474,7 +469,7 @@ describe('IntegrationBuilder', () => {
474
469
  Definition: {
475
470
  name: 'hubspot',
476
471
  webhooks: true,
477
- }
472
+ },
478
473
  },
479
474
  ],
480
475
  };
@@ -494,7 +489,7 @@ describe('IntegrationBuilder', () => {
494
489
  Definition: {
495
490
  name: 'salesforce',
496
491
  webhooks: { enabled: true },
497
- }
492
+ },
498
493
  },
499
494
  ],
500
495
  };
@@ -511,7 +506,7 @@ describe('IntegrationBuilder', () => {
511
506
  Definition: {
512
507
  name: 'slack',
513
508
  webhooks: false,
514
- }
509
+ },
515
510
  },
516
511
  ],
517
512
  };
@@ -528,7 +523,7 @@ describe('IntegrationBuilder', () => {
528
523
  Definition: {
529
524
  name: 'test',
530
525
  webhooks: { enabled: false },
531
- }
526
+ },
532
527
  },
533
528
  ],
534
529
  };
@@ -545,7 +540,7 @@ describe('IntegrationBuilder', () => {
545
540
  Definition: {
546
541
  name: 'stripe',
547
542
  webhooks: true,
548
- }
543
+ },
549
544
  },
550
545
  ],
551
546
  };
@@ -716,7 +711,7 @@ describe('IntegrationBuilder', () => {
716
711
  Definition: {
717
712
  name: 'asana',
718
713
  webhooks: true,
719
- }
714
+ },
720
715
  },
721
716
  ],
722
717
  };
@@ -742,7 +737,7 @@ describe('IntegrationBuilder', () => {
742
737
  Definition: {
743
738
  name: 'test',
744
739
  webhooks: true,
745
- }
740
+ },
746
741
  },
747
742
  ],
748
743
  };
@@ -751,9 +746,12 @@ describe('IntegrationBuilder', () => {
751
746
 
752
747
  const functionKeys = Object.keys(result.functions);
753
748
 
754
- // Expected order: dlqProcessor (from InternalErrorQueue), webhook, integration, queueWorker
749
+ // Expected order: dlqProcessor + userActionQueueWorker (both
750
+ // app-level, created before the integration loop), then webhook,
751
+ // integration, queueWorker.
755
752
  expect(functionKeys).toEqual([
756
753
  'dlqProcessor',
754
+ 'userActionQueueWorker',
757
755
  'testWebhook',
758
756
  'test',
759
757
  'testQueueWorker',
@@ -767,19 +765,19 @@ describe('IntegrationBuilder', () => {
767
765
  Definition: {
768
766
  name: 'hubspot',
769
767
  webhooks: true,
770
- }
768
+ },
771
769
  },
772
770
  {
773
771
  Definition: {
774
772
  name: 'salesforce',
775
773
  webhooks: false,
776
- }
774
+ },
777
775
  },
778
776
  {
779
777
  Definition: {
780
778
  name: 'slack',
781
779
  webhooks: { enabled: true },
782
- }
780
+ },
783
781
  },
784
782
  ],
785
783
  };
@@ -809,7 +807,7 @@ describe('IntegrationBuilder', () => {
809
807
  Definition: {
810
808
  name: 'test',
811
809
  webhooks: true,
812
- }
810
+ },
813
811
  },
814
812
  ],
815
813
  };
@@ -826,7 +824,7 @@ describe('IntegrationBuilder', () => {
826
824
  Definition: {
827
825
  name: 'test',
828
826
  webhooks: true,
829
- }
827
+ },
830
828
  },
831
829
  ],
832
830
  };
@@ -834,24 +832,26 @@ describe('IntegrationBuilder', () => {
834
832
  const result = await integrationBuilder.build(appDefinition, {});
835
833
 
836
834
  expect(result.functions.testWebhook.package).toBeDefined();
837
- expect(result.functions.testWebhook.package.exclude).toContain('node_modules/aws-sdk/**');
838
- expect(result.functions.testWebhook.package.exclude).toContain('node_modules/@prisma/**');
835
+ expect(result.functions.testWebhook.package.exclude).toContain(
836
+ 'node_modules/aws-sdk/**'
837
+ );
838
+ expect(result.functions.testWebhook.package.exclude).toContain(
839
+ 'node_modules/@prisma/**'
840
+ );
839
841
  });
840
842
  });
841
843
 
842
844
  describe('Prisma Layer Configuration', () => {
843
845
  it('should attach Prisma Lambda layer to queue worker functions', async () => {
844
846
  const appDefinition = {
845
- integrations: [
846
- { Definition: { name: 'hubspot' } },
847
- ],
847
+ integrations: [{ Definition: { name: 'hubspot' } }],
848
848
  };
849
849
 
850
850
  const result = await integrationBuilder.build(appDefinition, {});
851
851
 
852
852
  // Queue workers need Prisma layer for database operations
853
853
  expect(result.functions.hubspotQueueWorker.layers).toEqual([
854
- { Ref: 'PrismaLambdaLayer' }
854
+ { Ref: 'PrismaLambdaLayer' },
855
855
  ]);
856
856
  });
857
857
 
@@ -867,28 +867,26 @@ describe('IntegrationBuilder', () => {
867
867
  const result = await integrationBuilder.build(appDefinition, {});
868
868
 
869
869
  expect(result.functions.hubspotQueueWorker.layers).toEqual([
870
- { Ref: 'PrismaLambdaLayer' }
870
+ { Ref: 'PrismaLambdaLayer' },
871
871
  ]);
872
872
  expect(result.functions.salesforceQueueWorker.layers).toEqual([
873
- { Ref: 'PrismaLambdaLayer' }
873
+ { Ref: 'PrismaLambdaLayer' },
874
874
  ]);
875
875
  expect(result.functions.slackQueueWorker.layers).toEqual([
876
- { Ref: 'PrismaLambdaLayer' }
876
+ { Ref: 'PrismaLambdaLayer' },
877
877
  ]);
878
878
  });
879
879
 
880
880
  it('should attach Prisma layer to HTTP handlers for database access', async () => {
881
881
  const appDefinition = {
882
- integrations: [
883
- { Definition: { name: 'stripe' } },
884
- ],
882
+ integrations: [{ Definition: { name: 'stripe' } }],
885
883
  };
886
884
 
887
885
  const result = await integrationBuilder.build(appDefinition, {});
888
886
 
889
887
  // HTTP handlers also need Prisma for integration queries
890
888
  expect(result.functions.stripe.layers).toEqual([
891
- { Ref: 'PrismaLambdaLayer' }
889
+ { Ref: 'PrismaLambdaLayer' },
892
890
  ]);
893
891
  });
894
892
 
@@ -899,7 +897,7 @@ describe('IntegrationBuilder', () => {
899
897
  Definition: {
900
898
  name: 'hubspot',
901
899
  webhooks: true,
902
- }
900
+ },
903
901
  },
904
902
  ],
905
903
  };
@@ -908,7 +906,7 @@ describe('IntegrationBuilder', () => {
908
906
 
909
907
  // Webhook handlers need Prisma for credential lookups
910
908
  expect(result.functions.hubspotWebhook.layers).toEqual([
911
- { Ref: 'PrismaLambdaLayer' }
909
+ { Ref: 'PrismaLambdaLayer' },
912
910
  ]);
913
911
  });
914
912
 
@@ -935,5 +933,124 @@ describe('IntegrationBuilder', () => {
935
933
  );
936
934
  });
937
935
  });
938
- });
939
936
 
937
+ describe('User-Action FIFO queue', () => {
938
+ const appDefinition = {
939
+ integrations: [{ Definition: { name: 'test' } }],
940
+ };
941
+
942
+ it('creates a FIFO queue without ContentBasedDeduplication', async () => {
943
+ const result = await integrationBuilder.build(appDefinition, {});
944
+
945
+ const q = result.resources.FriggUserActionQueue;
946
+ expect(q).toBeDefined();
947
+ expect(q.Type).toBe('AWS::SQS::Queue');
948
+ expect(q.Properties.FifoQueue).toBe(true);
949
+ expect(q.Properties.ContentBasedDeduplication).toBeUndefined();
950
+ expect(q.Properties.VisibilityTimeout).toBe(1800);
951
+ expect(q.Properties.MessageRetentionPeriod).toBe(345600);
952
+ });
953
+
954
+ it('redrives to the FIFO DLQ with maxReceiveCount 2', async () => {
955
+ const result = await integrationBuilder.build(appDefinition, {});
956
+
957
+ expect(
958
+ result.resources.FriggUserActionQueue.Properties.RedrivePolicy
959
+ ).toEqual({
960
+ maxReceiveCount: 2,
961
+ deadLetterTargetArn: {
962
+ 'Fn::GetAtt': ['FriggUserActionDLQ', 'Arn'],
963
+ },
964
+ });
965
+ });
966
+
967
+ it('creates a FIFO DLQ with 14-day retention', async () => {
968
+ const result = await integrationBuilder.build(appDefinition, {});
969
+
970
+ const dlq = result.resources.FriggUserActionDLQ;
971
+ expect(dlq).toBeDefined();
972
+ expect(dlq.Properties.FifoQueue).toBe(true);
973
+ expect(dlq.Properties.MessageRetentionPeriod).toBe(1209600);
974
+ });
975
+
976
+ it('creates the worker bound to the FIFO queue ARN with no reservedConcurrency', async () => {
977
+ const result = await integrationBuilder.build(appDefinition, {});
978
+
979
+ const worker = result.functions.userActionQueueWorker;
980
+ expect(worker).toBeDefined();
981
+ expect(worker.handler).toBe(
982
+ 'node_modules/@friggframework/core/handlers/workers/user-action-worker.userActionQueueWorker'
983
+ );
984
+ expect(worker.timeout).toBe(900);
985
+ expect(worker.reservedConcurrency).toBeUndefined();
986
+ expect(worker.events).toEqual([
987
+ {
988
+ sqs: {
989
+ arn: { 'Fn::GetAtt': ['FriggUserActionQueue', 'Arn'] },
990
+ batchSize: 1,
991
+ functionResponseType: 'ReportBatchItemFailures',
992
+ },
993
+ },
994
+ ]);
995
+ });
996
+
997
+ it('exposes USER_ACTION_QUEUE_URL to all Lambdas', async () => {
998
+ const result = await integrationBuilder.build(appDefinition, {});
999
+
1000
+ expect(result.environment.USER_ACTION_QUEUE_URL).toEqual({
1001
+ Ref: 'FriggUserActionQueue',
1002
+ });
1003
+ });
1004
+
1005
+ it('alarms on the FIFO DLQ via the InternalErrorBridgeTopic', async () => {
1006
+ const result = await integrationBuilder.build(appDefinition, {});
1007
+
1008
+ const alarm = result.resources.FriggUserActionDLQAlarm;
1009
+ expect(alarm).toBeDefined();
1010
+ expect(alarm.Properties.AlarmActions).toEqual([
1011
+ { Ref: 'InternalErrorBridgeTopic' },
1012
+ ]);
1013
+ });
1014
+
1015
+ it('creates exactly one FIFO queue + worker regardless of integration count', async () => {
1016
+ const result = await integrationBuilder.build(
1017
+ {
1018
+ integrations: [
1019
+ { Definition: { name: 'hubspot' } },
1020
+ { Definition: { name: 'salesforce' } },
1021
+ { Definition: { name: 'slack' } },
1022
+ ],
1023
+ },
1024
+ {}
1025
+ );
1026
+
1027
+ const fifoQueues = Object.keys(result.resources).filter(
1028
+ (k) => k === 'FriggUserActionQueue'
1029
+ );
1030
+ expect(fifoQueues).toHaveLength(1);
1031
+ expect(result.functions.userActionQueueWorker).toBeDefined();
1032
+ });
1033
+
1034
+ it('does not make per-integration queues FIFO', async () => {
1035
+ const result = await integrationBuilder.build(appDefinition, {});
1036
+
1037
+ expect(
1038
+ result.resources.TestQueue.Properties.FifoQueue
1039
+ ).toBeUndefined();
1040
+ });
1041
+
1042
+ it('omits the Prisma layer on the worker when usePrismaLambdaLayer=false', async () => {
1043
+ const result = await integrationBuilder.build(
1044
+ {
1045
+ usePrismaLambdaLayer: false,
1046
+ integrations: [{ Definition: { name: 'test' } }],
1047
+ },
1048
+ {}
1049
+ );
1050
+
1051
+ expect(
1052
+ result.functions.userActionQueueWorker.layers
1053
+ ).toBeUndefined();
1054
+ });
1055
+ });
1056
+ });
@@ -14,7 +14,7 @@ function generateIAMCloudFormation(options = {}) {
14
14
  appName = 'Frigg',
15
15
  features = {},
16
16
  userPrefix = 'frigg-deployment-user',
17
- stackName = 'frigg-deployment-iam'
17
+ stackName = 'frigg-deployment-iam',
18
18
  } = options;
19
19
 
20
20
  const deploymentUserName = userPrefix;
@@ -426,6 +426,10 @@ function generateIAMCloudFormation(options = {}) {
426
426
  ],
427
427
  Resource: [
428
428
  { 'Fn::Sub': 'arn:aws:sqs:*:${AWS::AccountId}:*frigg*' },
429
+ // IAM wildcard matching is case-sensitive; the app-level FIFO
430
+ // queue is named "...-FriggUserActionQueue.fifo", which the
431
+ // lowercase "*frigg*" glob would not match.
432
+ { 'Fn::Sub': 'arn:aws:sqs:*:${AWS::AccountId}:*Frigg*' },
429
433
  {
430
434
  'Fn::Sub':
431
435
  'arn:aws:sqs:*:${AWS::AccountId}:internal-error-queue-*',
@@ -761,8 +765,7 @@ function getFeatureSummary(appDefinition) {
761
765
  const features = {
762
766
  core: true, // Always enabled
763
767
  vpc: appDefinition.vpc?.enable === true,
764
- kms:
765
- appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms',
768
+ kms: appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms',
766
769
  ssm: appDefinition.ssm?.enable === true,
767
770
  websockets: appDefinition.websockets?.enable === true,
768
771
  };
@@ -781,7 +784,10 @@ function getFeatureSummary(appDefinition) {
781
784
  * @returns {Object} Basic IAM policy document
782
785
  */
783
786
  function generateBasicIAMPolicy() {
784
- const basicPolicyPath = path.join(__dirname, 'templates/iam-policy-basic.json');
787
+ const basicPolicyPath = path.join(
788
+ __dirname,
789
+ 'templates/iam-policy-basic.json'
790
+ );
785
791
  return require(basicPolicyPath);
786
792
  }
787
793
 
@@ -790,7 +796,10 @@ function generateBasicIAMPolicy() {
790
796
  * @returns {Object} Full IAM policy document
791
797
  */
792
798
  function generateFullIAMPolicy() {
793
- const fullPolicyPath = path.join(__dirname, 'templates/iam-policy-full.json');
799
+ const fullPolicyPath = path.join(
800
+ __dirname,
801
+ 'templates/iam-policy-full.json'
802
+ );
794
803
  return require(fullPolicyPath);
795
804
  }
796
805