@friggframework/devtools 2.0.0-next.8 → 2.0.0-next.80

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.
Files changed (240) hide show
  1. package/frigg-cli/README.md +1289 -0
  2. package/frigg-cli/__tests__/unit/commands/build.test.js +279 -0
  3. package/frigg-cli/__tests__/unit/commands/db-setup.test.js +649 -0
  4. package/frigg-cli/__tests__/unit/commands/deploy.test.js +320 -0
  5. package/frigg-cli/__tests__/unit/commands/doctor.test.js +309 -0
  6. package/frigg-cli/__tests__/unit/commands/install.test.js +400 -0
  7. package/frigg-cli/__tests__/unit/commands/ui.test.js +346 -0
  8. package/frigg-cli/__tests__/unit/dependencies.test.js +74 -0
  9. package/frigg-cli/__tests__/unit/utils/database-validator.test.js +397 -0
  10. package/frigg-cli/__tests__/unit/utils/error-messages.test.js +345 -0
  11. package/frigg-cli/__tests__/unit/version-detection.test.js +171 -0
  12. package/frigg-cli/__tests__/utils/mock-factory.js +270 -0
  13. package/frigg-cli/__tests__/utils/prisma-mock.js +194 -0
  14. package/frigg-cli/__tests__/utils/test-fixtures.js +463 -0
  15. package/frigg-cli/__tests__/utils/test-setup.js +287 -0
  16. package/frigg-cli/auth-command/CLAUDE.md +293 -0
  17. package/frigg-cli/auth-command/README.md +450 -0
  18. package/frigg-cli/auth-command/api-key-flow.js +153 -0
  19. package/frigg-cli/auth-command/auth-tester.js +344 -0
  20. package/frigg-cli/auth-command/credential-storage.js +182 -0
  21. package/frigg-cli/auth-command/index.js +256 -0
  22. package/frigg-cli/auth-command/json-schema-form.js +67 -0
  23. package/frigg-cli/auth-command/module-loader.js +172 -0
  24. package/frigg-cli/auth-command/oauth-callback-server.js +431 -0
  25. package/frigg-cli/auth-command/oauth-flow.js +195 -0
  26. package/frigg-cli/auth-command/utils/browser.js +30 -0
  27. package/frigg-cli/build-command/index.js +45 -12
  28. package/frigg-cli/db-setup-command/index.js +246 -0
  29. package/frigg-cli/deploy-command/SPEC-DEPLOY-DRY-RUN.md +981 -0
  30. package/frigg-cli/deploy-command/index.js +295 -23
  31. package/frigg-cli/doctor-command/index.js +335 -0
  32. package/frigg-cli/generate-command/__tests__/generate-command.test.js +301 -0
  33. package/frigg-cli/generate-command/azure-generator.js +43 -0
  34. package/frigg-cli/generate-command/gcp-generator.js +47 -0
  35. package/frigg-cli/generate-command/index.js +332 -0
  36. package/frigg-cli/generate-command/terraform-generator.js +555 -0
  37. package/frigg-cli/generate-iam-command.js +118 -0
  38. package/frigg-cli/index.js +174 -1
  39. package/frigg-cli/index.test.js +1 -4
  40. package/frigg-cli/init-command/backend-first-handler.js +756 -0
  41. package/frigg-cli/init-command/index.js +93 -0
  42. package/frigg-cli/init-command/template-handler.js +143 -0
  43. package/frigg-cli/install-command/index.js +1 -4
  44. package/frigg-cli/jest.config.js +124 -0
  45. package/frigg-cli/package.json +63 -0
  46. package/frigg-cli/repair-command/index.js +564 -0
  47. package/frigg-cli/start-command/index.js +118 -5
  48. package/frigg-cli/start-command/start-command.test.js +297 -0
  49. package/frigg-cli/test/init-command.test.js +180 -0
  50. package/frigg-cli/test/npm-registry.test.js +319 -0
  51. package/frigg-cli/ui-command/index.js +154 -0
  52. package/frigg-cli/utils/app-resolver.js +319 -0
  53. package/frigg-cli/utils/backend-path.js +16 -17
  54. package/frigg-cli/utils/database-validator.js +167 -0
  55. package/frigg-cli/utils/error-messages.js +329 -0
  56. package/frigg-cli/utils/npm-registry.js +167 -0
  57. package/frigg-cli/utils/process-manager.js +199 -0
  58. package/frigg-cli/utils/repo-detection.js +405 -0
  59. package/infrastructure/ARCHITECTURE.md +487 -0
  60. package/infrastructure/CLAUDE.md +481 -0
  61. package/infrastructure/HEALTH.md +468 -0
  62. package/infrastructure/README.md +522 -0
  63. package/infrastructure/__tests__/fixtures/mock-aws-resources.js +391 -0
  64. package/infrastructure/__tests__/helpers/test-utils.js +277 -0
  65. package/infrastructure/__tests__/postgres-config.test.js +914 -0
  66. package/infrastructure/__tests__/template-generation.test.js +687 -0
  67. package/infrastructure/create-frigg-infrastructure.js +129 -20
  68. package/infrastructure/docs/POSTGRES-CONFIGURATION.md +630 -0
  69. package/infrastructure/docs/PRE-DEPLOYMENT-HEALTH-CHECK-SPEC.md +1317 -0
  70. package/infrastructure/docs/WEBSOCKET-CONFIGURATION.md +105 -0
  71. package/infrastructure/docs/deployment-instructions.md +268 -0
  72. package/infrastructure/docs/generate-iam-command.md +278 -0
  73. package/infrastructure/docs/iam-policy-templates.md +193 -0
  74. package/infrastructure/domains/database/aurora-builder.js +809 -0
  75. package/infrastructure/domains/database/aurora-builder.test.js +950 -0
  76. package/infrastructure/domains/database/aurora-discovery.js +87 -0
  77. package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
  78. package/infrastructure/domains/database/aurora-resolver.js +210 -0
  79. package/infrastructure/domains/database/aurora-resolver.test.js +347 -0
  80. package/infrastructure/domains/database/migration-builder.js +701 -0
  81. package/infrastructure/domains/database/migration-builder.test.js +321 -0
  82. package/infrastructure/domains/database/migration-resolver.js +163 -0
  83. package/infrastructure/domains/database/migration-resolver.test.js +337 -0
  84. package/infrastructure/domains/health/application/ports/IPropertyReconciler.js +164 -0
  85. package/infrastructure/domains/health/application/ports/IResourceDetector.js +129 -0
  86. package/infrastructure/domains/health/application/ports/IResourceImporter.js +142 -0
  87. package/infrastructure/domains/health/application/ports/IStackRepository.js +131 -0
  88. package/infrastructure/domains/health/application/ports/index.js +26 -0
  89. package/infrastructure/domains/health/application/use-cases/__tests__/execute-resource-import-use-case.test.js +679 -0
  90. package/infrastructure/domains/health/application/use-cases/__tests__/mismatch-analyzer-method-name.test.js +167 -0
  91. package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +1130 -0
  92. package/infrastructure/domains/health/application/use-cases/execute-resource-import-use-case.js +221 -0
  93. package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +152 -0
  94. package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.test.js +343 -0
  95. package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +535 -0
  96. package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.test.js +376 -0
  97. package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +213 -0
  98. package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +441 -0
  99. package/infrastructure/domains/health/docs/ACME-DEV-DRIFT-ANALYSIS.md +267 -0
  100. package/infrastructure/domains/health/docs/BUILD-VS-DEPLOYED-TEMPLATE-ANALYSIS.md +324 -0
  101. package/infrastructure/domains/health/docs/ORPHAN-DETECTION-ANALYSIS.md +386 -0
  102. package/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md +1419 -0
  103. package/infrastructure/domains/health/docs/TDD-IMPLEMENTATION-SUMMARY.md +391 -0
  104. package/infrastructure/domains/health/docs/TEMPLATE-COMPARISON-IMPLEMENTATION.md +551 -0
  105. package/infrastructure/domains/health/domain/entities/issue.js +299 -0
  106. package/infrastructure/domains/health/domain/entities/issue.test.js +528 -0
  107. package/infrastructure/domains/health/domain/entities/property-mismatch.js +108 -0
  108. package/infrastructure/domains/health/domain/entities/property-mismatch.test.js +275 -0
  109. package/infrastructure/domains/health/domain/entities/resource.js +159 -0
  110. package/infrastructure/domains/health/domain/entities/resource.test.js +432 -0
  111. package/infrastructure/domains/health/domain/entities/stack-health-report.js +306 -0
  112. package/infrastructure/domains/health/domain/entities/stack-health-report.test.js +601 -0
  113. package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +380 -0
  114. package/infrastructure/domains/health/domain/services/__tests__/import-progress-monitor.test.js +971 -0
  115. package/infrastructure/domains/health/domain/services/__tests__/import-template-generator.test.js +1150 -0
  116. package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +672 -0
  117. package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +496 -0
  118. package/infrastructure/domains/health/domain/services/__tests__/update-progress-monitor.test.js +419 -0
  119. package/infrastructure/domains/health/domain/services/health-score-calculator.js +248 -0
  120. package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +504 -0
  121. package/infrastructure/domains/health/domain/services/import-progress-monitor.js +195 -0
  122. package/infrastructure/domains/health/domain/services/import-template-generator.js +435 -0
  123. package/infrastructure/domains/health/domain/services/logical-id-mapper.js +345 -0
  124. package/infrastructure/domains/health/domain/services/mismatch-analyzer.js +234 -0
  125. package/infrastructure/domains/health/domain/services/mismatch-analyzer.test.js +431 -0
  126. package/infrastructure/domains/health/domain/services/property-mutability-config.js +382 -0
  127. package/infrastructure/domains/health/domain/services/template-parser.js +245 -0
  128. package/infrastructure/domains/health/domain/services/update-progress-monitor.js +192 -0
  129. package/infrastructure/domains/health/domain/value-objects/health-score.js +138 -0
  130. package/infrastructure/domains/health/domain/value-objects/health-score.test.js +267 -0
  131. package/infrastructure/domains/health/domain/value-objects/property-mutability.js +161 -0
  132. package/infrastructure/domains/health/domain/value-objects/property-mutability.test.js +198 -0
  133. package/infrastructure/domains/health/domain/value-objects/resource-state.js +167 -0
  134. package/infrastructure/domains/health/domain/value-objects/resource-state.test.js +196 -0
  135. package/infrastructure/domains/health/domain/value-objects/stack-identifier.js +192 -0
  136. package/infrastructure/domains/health/domain/value-objects/stack-identifier.test.js +262 -0
  137. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-cfn-tagged.test.js +312 -0
  138. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-multi-stack.test.js +367 -0
  139. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-relationship-analysis.test.js +432 -0
  140. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.js +784 -0
  141. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +1133 -0
  142. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +565 -0
  143. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +554 -0
  144. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.js +318 -0
  145. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.test.js +398 -0
  146. package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +777 -0
  147. package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.test.js +580 -0
  148. package/infrastructure/domains/integration/integration-builder.js +547 -0
  149. package/infrastructure/domains/integration/integration-builder.test.js +798 -0
  150. package/infrastructure/domains/integration/integration-resolver.js +170 -0
  151. package/infrastructure/domains/integration/integration-resolver.test.js +369 -0
  152. package/infrastructure/domains/integration/websocket-builder.js +69 -0
  153. package/infrastructure/domains/integration/websocket-builder.test.js +195 -0
  154. package/infrastructure/domains/networking/vpc-builder.js +2051 -0
  155. package/infrastructure/domains/networking/vpc-builder.test.js +1960 -0
  156. package/infrastructure/domains/networking/vpc-discovery.js +177 -0
  157. package/infrastructure/domains/networking/vpc-discovery.test.js +350 -0
  158. package/infrastructure/domains/networking/vpc-resolver.js +505 -0
  159. package/infrastructure/domains/networking/vpc-resolver.test.js +801 -0
  160. package/infrastructure/domains/parameters/ssm-builder.js +79 -0
  161. package/infrastructure/domains/parameters/ssm-builder.test.js +189 -0
  162. package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
  163. package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
  164. package/infrastructure/domains/scheduler/scheduler-builder.js +211 -0
  165. package/infrastructure/domains/security/iam-generator.js +816 -0
  166. package/infrastructure/domains/security/iam-generator.test.js +204 -0
  167. package/infrastructure/domains/security/kms-builder.js +415 -0
  168. package/infrastructure/domains/security/kms-builder.test.js +392 -0
  169. package/infrastructure/domains/security/kms-discovery.js +80 -0
  170. package/infrastructure/domains/security/kms-discovery.test.js +177 -0
  171. package/infrastructure/domains/security/kms-resolver.js +96 -0
  172. package/infrastructure/domains/security/kms-resolver.test.js +216 -0
  173. package/infrastructure/domains/security/templates/frigg-deployment-iam-stack.yaml +401 -0
  174. package/infrastructure/domains/security/templates/iam-policy-basic.json +218 -0
  175. package/infrastructure/domains/security/templates/iam-policy-full.json +288 -0
  176. package/infrastructure/domains/shared/base-builder.js +112 -0
  177. package/infrastructure/domains/shared/base-resolver.js +186 -0
  178. package/infrastructure/domains/shared/base-resolver.test.js +305 -0
  179. package/infrastructure/domains/shared/builder-orchestrator.js +212 -0
  180. package/infrastructure/domains/shared/builder-orchestrator.test.js +213 -0
  181. package/infrastructure/domains/shared/cloudformation-discovery-v2.js +334 -0
  182. package/infrastructure/domains/shared/cloudformation-discovery.js +681 -0
  183. package/infrastructure/domains/shared/cloudformation-discovery.test.js +1320 -0
  184. package/infrastructure/domains/shared/environment-builder.js +119 -0
  185. package/infrastructure/domains/shared/environment-builder.test.js +247 -0
  186. package/infrastructure/domains/shared/providers/aws-provider-adapter.js +579 -0
  187. package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +416 -0
  188. package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
  189. package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
  190. package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
  191. package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
  192. package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
  193. package/infrastructure/domains/shared/resource-discovery.enhanced.test.js +306 -0
  194. package/infrastructure/domains/shared/resource-discovery.js +256 -0
  195. package/infrastructure/domains/shared/resource-discovery.test.js +757 -0
  196. package/infrastructure/domains/shared/types/app-definition.js +205 -0
  197. package/infrastructure/domains/shared/types/discovery-result.js +106 -0
  198. package/infrastructure/domains/shared/types/discovery-result.test.js +258 -0
  199. package/infrastructure/domains/shared/types/index.js +46 -0
  200. package/infrastructure/domains/shared/types/resource-ownership.js +108 -0
  201. package/infrastructure/domains/shared/types/resource-ownership.test.js +101 -0
  202. package/infrastructure/domains/shared/utilities/base-definition-factory.js +408 -0
  203. package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
  204. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +291 -0
  205. package/infrastructure/domains/shared/utilities/handler-path-resolver.js +134 -0
  206. package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +268 -0
  207. package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +159 -0
  208. package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +444 -0
  209. package/infrastructure/domains/shared/validation/env-validator.js +78 -0
  210. package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
  211. package/infrastructure/domains/shared/validation/plugin-validator.js +187 -0
  212. package/infrastructure/domains/shared/validation/plugin-validator.test.js +323 -0
  213. package/infrastructure/esbuild.config.js +53 -0
  214. package/infrastructure/infrastructure-composer.js +119 -0
  215. package/infrastructure/infrastructure-composer.test.js +1896 -0
  216. package/infrastructure/integration.test.js +383 -0
  217. package/infrastructure/scripts/build-prisma-layer.js +701 -0
  218. package/infrastructure/scripts/build-prisma-layer.test.js +170 -0
  219. package/infrastructure/scripts/build-time-discovery.js +238 -0
  220. package/infrastructure/scripts/build-time-discovery.test.js +379 -0
  221. package/infrastructure/scripts/run-discovery.js +110 -0
  222. package/infrastructure/scripts/verify-prisma-layer.js +72 -0
  223. package/management-ui/README.md +203 -0
  224. package/package.json +44 -14
  225. package/test/index.js +2 -4
  226. package/test/mock-api.js +1 -3
  227. package/test/mock-integration.js +4 -14
  228. package/.eslintrc.json +0 -3
  229. package/CHANGELOG.md +0 -132
  230. package/infrastructure/app-handler-helpers.js +0 -57
  231. package/infrastructure/backend-utils.js +0 -87
  232. package/infrastructure/routers/auth.js +0 -26
  233. package/infrastructure/routers/integration-defined-routers.js +0 -42
  234. package/infrastructure/routers/middleware/loadUser.js +0 -15
  235. package/infrastructure/routers/middleware/requireLoggedInUser.js +0 -12
  236. package/infrastructure/routers/user.js +0 -41
  237. package/infrastructure/routers/websocket.js +0 -55
  238. package/infrastructure/serverless-template.js +0 -291
  239. package/infrastructure/workers/integration-defined-workers.js +0 -24
  240. package/test/auther-definition-tester.js +0 -125
@@ -0,0 +1,798 @@
1
+ /**
2
+ * Tests for Integration Builder
3
+ *
4
+ * Tests integration-specific Lambda functions and SQS queues
5
+ */
6
+
7
+ const { IntegrationBuilder } = require('./integration-builder');
8
+ const { ValidationResult } = require('../shared/base-builder');
9
+
10
+ describe('IntegrationBuilder', () => {
11
+ let integrationBuilder;
12
+
13
+ beforeEach(() => {
14
+ integrationBuilder = new IntegrationBuilder();
15
+ });
16
+
17
+ describe('shouldExecute()', () => {
18
+ it('should return true when integrations array has items', () => {
19
+ const appDefinition = {
20
+ integrations: [
21
+ { Definition: { name: 'test' } },
22
+ ],
23
+ };
24
+
25
+ expect(integrationBuilder.shouldExecute(appDefinition)).toBe(true);
26
+ });
27
+
28
+ it('should return false when integrations array is empty', () => {
29
+ const appDefinition = {
30
+ integrations: [],
31
+ };
32
+
33
+ expect(integrationBuilder.shouldExecute(appDefinition)).toBe(false);
34
+ });
35
+
36
+ it('should return false when integrations is not defined', () => {
37
+ const appDefinition = {};
38
+
39
+ expect(integrationBuilder.shouldExecute(appDefinition)).toBe(false);
40
+ });
41
+
42
+ it('should return false when integrations is not an array', () => {
43
+ const appDefinition = {
44
+ integrations: { name: 'test' },
45
+ };
46
+
47
+ expect(integrationBuilder.shouldExecute(appDefinition)).toBe(false);
48
+ });
49
+ });
50
+
51
+ describe('validate()', () => {
52
+ it('should pass validation for valid integrations', () => {
53
+ const appDefinition = {
54
+ integrations: [
55
+ { Definition: { name: 'hubspot' } },
56
+ { Definition: { name: 'salesforce' } },
57
+ ],
58
+ };
59
+
60
+ const result = integrationBuilder.validate(appDefinition);
61
+
62
+ expect(result).toBeInstanceOf(ValidationResult);
63
+ expect(result.valid).toBe(true);
64
+ expect(result.errors).toEqual([]);
65
+ });
66
+
67
+ it('should pass when integrations is undefined', () => {
68
+ const appDefinition = {};
69
+
70
+ const result = integrationBuilder.validate(appDefinition);
71
+
72
+ expect(result.valid).toBe(true);
73
+ });
74
+
75
+ it('should error when integrations is not an array', () => {
76
+ const appDefinition = {
77
+ integrations: 'invalid',
78
+ };
79
+
80
+ const result = integrationBuilder.validate(appDefinition);
81
+
82
+ expect(result.valid).toBe(false);
83
+ expect(result.errors).toContain('integrations must be an array');
84
+ });
85
+
86
+ it('should error when integration is missing Definition', () => {
87
+ const appDefinition = {
88
+ integrations: [
89
+ { someOtherField: 'value' },
90
+ ],
91
+ };
92
+
93
+ const result = integrationBuilder.validate(appDefinition);
94
+
95
+ expect(result.valid).toBe(false);
96
+ expect(result.errors).toContain(
97
+ 'Integration at index 0 is missing Definition or name'
98
+ );
99
+ });
100
+
101
+ it('should error when integration Definition is missing name', () => {
102
+ const appDefinition = {
103
+ integrations: [
104
+ { Definition: {} },
105
+ ],
106
+ };
107
+
108
+ const result = integrationBuilder.validate(appDefinition);
109
+
110
+ expect(result.valid).toBe(false);
111
+ expect(result.errors).toContain(
112
+ 'Integration at index 0 is missing Definition or name'
113
+ );
114
+ });
115
+
116
+ it('should validate all integrations', () => {
117
+ const appDefinition = {
118
+ integrations: [
119
+ { Definition: { name: 'valid' } },
120
+ { Definition: {} }, // Invalid - no name
121
+ { someField: 'value' }, // Invalid - no Definition
122
+ ],
123
+ };
124
+
125
+ const result = integrationBuilder.validate(appDefinition);
126
+
127
+ expect(result.valid).toBe(false);
128
+ expect(result.errors).toHaveLength(2);
129
+ });
130
+ });
131
+
132
+ describe('build()', () => {
133
+ it('should create HTTP handler for integration', async () => {
134
+ const appDefinition = {
135
+ integrations: [
136
+ { Definition: { name: 'hubspot' } },
137
+ ],
138
+ };
139
+
140
+ const result = await integrationBuilder.build(appDefinition, {});
141
+
142
+ expect(result.functions.hubspot).toBeDefined();
143
+ expect(result.functions.hubspot.handler).toBe(
144
+ 'node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.hubspot.handler'
145
+ );
146
+ });
147
+
148
+ it('should configure HTTP API event for integration', async () => {
149
+ const appDefinition = {
150
+ integrations: [
151
+ { Definition: { name: 'salesforce' } },
152
+ ],
153
+ };
154
+
155
+ const result = await integrationBuilder.build(appDefinition, {});
156
+
157
+ expect(result.functions.salesforce.events).toEqual([
158
+ {
159
+ httpApi: {
160
+ path: '/api/salesforce-integration/{proxy+}',
161
+ method: 'ANY',
162
+ },
163
+ },
164
+ ]);
165
+ });
166
+
167
+ it('should create SQS queue for integration', async () => {
168
+ const appDefinition = {
169
+ integrations: [
170
+ { Definition: { name: 'slack' } },
171
+ ],
172
+ };
173
+
174
+ const result = await integrationBuilder.build(appDefinition, {});
175
+
176
+ expect(result.resources.SlackQueue).toBeDefined();
177
+ expect(result.resources.SlackQueue.Type).toBe('AWS::SQS::Queue');
178
+ });
179
+
180
+ it('should configure queue with correct retention and visibility timeout', async () => {
181
+ const appDefinition = {
182
+ integrations: [
183
+ { Definition: { name: 'test' } },
184
+ ],
185
+ };
186
+
187
+ const result = await integrationBuilder.build(appDefinition, {});
188
+
189
+ expect(result.resources.TestQueue.Properties.MessageRetentionPeriod).toBe(345600);
190
+ expect(result.resources.TestQueue.Properties.VisibilityTimeout).toBe(1800);
191
+ });
192
+
193
+ it('should configure redrive policy to internal error queue', async () => {
194
+ const appDefinition = {
195
+ integrations: [
196
+ { Definition: { name: 'test' } },
197
+ ],
198
+ };
199
+
200
+ const result = await integrationBuilder.build(appDefinition, {});
201
+
202
+ expect(result.resources.TestQueue.Properties.RedrivePolicy).toEqual({
203
+ maxReceiveCount: 3,
204
+ deadLetterTargetArn: {
205
+ 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
206
+ },
207
+ });
208
+ });
209
+
210
+ it('should create queue worker function', async () => {
211
+ const appDefinition = {
212
+ integrations: [
213
+ { Definition: { name: 'hubspot' } },
214
+ ],
215
+ };
216
+
217
+ const result = await integrationBuilder.build(appDefinition, {});
218
+
219
+ expect(result.functions.hubspotQueueWorker).toBeDefined();
220
+ });
221
+
222
+ it('should configure queue worker with SQS event', async () => {
223
+ const appDefinition = {
224
+ integrations: [
225
+ { Definition: { name: 'test' } },
226
+ ],
227
+ };
228
+
229
+ const result = await integrationBuilder.build(appDefinition, {});
230
+
231
+ expect(result.functions.testQueueWorker.events).toEqual([
232
+ {
233
+ sqs: {
234
+ arn: { 'Fn::GetAtt': ['TestQueue', 'Arn'] },
235
+ batchSize: 1,
236
+ functionResponseType: 'ReportBatchItemFailures',
237
+ },
238
+ },
239
+ ]);
240
+ });
241
+
242
+ it('should set queue worker timeout to 600 seconds', async () => {
243
+ const appDefinition = {
244
+ integrations: [
245
+ { Definition: { name: 'test' } },
246
+ ],
247
+ };
248
+
249
+ const result = await integrationBuilder.build(appDefinition, {});
250
+
251
+ expect(result.functions.testQueueWorker.timeout).toBe(900); // 15 minutes (Lambda max)
252
+ });
253
+
254
+ it('should set queue worker reserved concurrency', async () => {
255
+ const appDefinition = {
256
+ integrations: [
257
+ { Definition: { name: 'test' } },
258
+ ],
259
+ };
260
+
261
+ const result = await integrationBuilder.build(appDefinition, {});
262
+
263
+ expect(result.functions.testQueueWorker.reservedConcurrency).toBe(20);
264
+ });
265
+
266
+ it('should add queue URL to environment variables', async () => {
267
+ const appDefinition = {
268
+ integrations: [
269
+ { Definition: { name: 'slack' } },
270
+ ],
271
+ };
272
+
273
+ const result = await integrationBuilder.build(appDefinition, {});
274
+
275
+ expect(result.environment.SLACK_QUEUE_URL).toEqual({
276
+ Ref: 'SlackQueue',
277
+ });
278
+ });
279
+
280
+ it('should add queue name to custom variables', async () => {
281
+ const appDefinition = {
282
+ integrations: [
283
+ { Definition: { name: 'stripe' } },
284
+ ],
285
+ };
286
+
287
+ const result = await integrationBuilder.build(appDefinition, {});
288
+
289
+ expect(result.custom.StripeQueue).toBe('${self:service}--${self:provider.stage}-StripeQueue');
290
+ });
291
+
292
+ it('should handle multiple integrations', async () => {
293
+ const appDefinition = {
294
+ integrations: [
295
+ { Definition: { name: 'hubspot' } },
296
+ { Definition: { name: 'salesforce' } },
297
+ { Definition: { name: 'slack' } },
298
+ ],
299
+ };
300
+
301
+ const result = await integrationBuilder.build(appDefinition, {});
302
+
303
+ // Check functions
304
+ expect(result.functions.hubspot).toBeDefined();
305
+ expect(result.functions.salesforce).toBeDefined();
306
+ expect(result.functions.slack).toBeDefined();
307
+ expect(result.functions.hubspotQueueWorker).toBeDefined();
308
+ expect(result.functions.salesforceQueueWorker).toBeDefined();
309
+ expect(result.functions.slackQueueWorker).toBeDefined();
310
+
311
+ // Check queues
312
+ expect(result.resources.HubspotQueue).toBeDefined();
313
+ expect(result.resources.SalesforceQueue).toBeDefined();
314
+ expect(result.resources.SlackQueue).toBeDefined();
315
+
316
+ // Check environment vars
317
+ expect(result.environment.HUBSPOT_QUEUE_URL).toBeDefined();
318
+ expect(result.environment.SALESFORCE_QUEUE_URL).toBeDefined();
319
+ expect(result.environment.SLACK_QUEUE_URL).toBeDefined();
320
+ });
321
+
322
+ it('should capitalize integration name for queue reference', async () => {
323
+ const appDefinition = {
324
+ integrations: [
325
+ { Definition: { name: 'myIntegration' } },
326
+ ],
327
+ };
328
+
329
+ const result = await integrationBuilder.build(appDefinition, {});
330
+
331
+ // Queue name should start with capital letter
332
+ expect(result.resources.MyIntegrationQueue).toBeDefined();
333
+ });
334
+
335
+ it('should handle integration names with hyphens', async () => {
336
+ const appDefinition = {
337
+ integrations: [
338
+ { Definition: { name: 'my-integration' } },
339
+ ],
340
+ };
341
+
342
+ const result = await integrationBuilder.build(appDefinition, {});
343
+
344
+ expect(result.functions['my-integration']).toBeDefined();
345
+ expect(result.functions['my-integrationQueueWorker']).toBeDefined();
346
+ });
347
+
348
+ // ============================================================
349
+ // Theory-proving tests: demonstrate current dangerous config
350
+ // These tests document the root cause of the Modern Midstay bug
351
+ // where POST_CREATE_SETUP messages were silently lost.
352
+ // ============================================================
353
+
354
+ it('THEORY: MessageRetentionPeriod is too short for delayed messages', async () => {
355
+ // POST_CREATE_SETUP uses DelaySeconds=35.
356
+ // With MessageRetentionPeriod=60, the message is only visible
357
+ // for 25 seconds before SQS silently deletes it.
358
+ // Messages that expire are NOT sent to DLQ — they vanish.
359
+ const appDefinition = {
360
+ integrations: [{ Definition: { name: 'test' } }],
361
+ };
362
+
363
+ const result = await integrationBuilder.build(appDefinition, {});
364
+ const retention = result.resources.TestQueue.Properties.MessageRetentionPeriod;
365
+
366
+ // The max SQS DelaySeconds is 900. Retention must comfortably
367
+ // exceed this to ensure delayed messages are never silently lost.
368
+ // Current value (60) fails this check.
369
+ expect(retention).toBeGreaterThan(900);
370
+ });
371
+
372
+ it('THEORY: maxReceiveCount=1 means zero retries on transient failures', async () => {
373
+ // A single transient error (network blip, cold start timeout,
374
+ // rate limit) sends the message straight to DLQ with no retry.
375
+ const appDefinition = {
376
+ integrations: [{ Definition: { name: 'test' } }],
377
+ };
378
+
379
+ const result = await integrationBuilder.build(appDefinition, {});
380
+ const maxReceiveCount = result.resources.TestQueue.Properties.RedrivePolicy.maxReceiveCount;
381
+
382
+ // Should allow at least 2 retries (maxReceiveCount >= 3)
383
+ expect(maxReceiveCount).toBeGreaterThanOrEqual(3);
384
+ });
385
+
386
+ it('THEORY: SQS event source should enable ReportBatchItemFailures', async () => {
387
+ // Without this, Lambda can't tell SQS which specific messages
388
+ // failed — it's all-or-nothing for the entire invocation.
389
+ const appDefinition = {
390
+ integrations: [{ Definition: { name: 'test' } }],
391
+ };
392
+
393
+ const result = await integrationBuilder.build(appDefinition, {});
394
+ const sqsEvent = result.functions.testQueueWorker.events[0].sqs;
395
+
396
+ expect(sqsEvent.functionResponseType).toBe('ReportBatchItemFailures');
397
+ });
398
+ });
399
+
400
+ describe('DLQ Observability', () => {
401
+ it('should create a CloudWatch alarm for DLQ message depth', async () => {
402
+ const appDefinition = {
403
+ integrations: [{ Definition: { name: 'test' } }],
404
+ };
405
+
406
+ const result = await integrationBuilder.build(appDefinition, {});
407
+
408
+ 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);
413
+ });
414
+
415
+ it('should wire alarm to InternalErrorBridgeTopic for notifications', async () => {
416
+ const appDefinition = {
417
+ integrations: [{ Definition: { name: 'test' } }],
418
+ };
419
+
420
+ const result = await integrationBuilder.build(appDefinition, {});
421
+
422
+ expect(result.resources.DLQMessageAlarm.Properties.AlarmActions).toEqual([
423
+ { Ref: 'InternalErrorBridgeTopic' },
424
+ ]);
425
+ });
426
+
427
+ it('should create a DLQ processor Lambda triggered by InternalErrorQueue', async () => {
428
+ const appDefinition = {
429
+ integrations: [{ Definition: { name: 'test' } }],
430
+ };
431
+
432
+ const result = await integrationBuilder.build(appDefinition, {});
433
+
434
+ expect(result.functions.dlqProcessor).toBeDefined();
435
+ expect(result.functions.dlqProcessor.events[0].sqs.arn).toEqual({
436
+ 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
437
+ });
438
+ expect(result.functions.dlqProcessor.events[0].sqs.functionResponseType).toBe('ReportBatchItemFailures');
439
+ });
440
+
441
+ it('DLQ processor should have skipEsbuild, short timeout, and low concurrency', async () => {
442
+ const appDefinition = {
443
+ integrations: [{ Definition: { name: 'test' } }],
444
+ };
445
+
446
+ const result = await integrationBuilder.build(appDefinition, {});
447
+
448
+ expect(result.functions.dlqProcessor.skipEsbuild).toBe(true);
449
+ expect(result.functions.dlqProcessor.package).toBeDefined();
450
+ expect(result.functions.dlqProcessor.timeout).toBeLessThanOrEqual(60);
451
+ expect(result.functions.dlqProcessor.reservedConcurrency).toBe(1);
452
+ });
453
+ });
454
+
455
+ describe('getDependencies()', () => {
456
+ it('should have no dependencies', () => {
457
+ const deps = integrationBuilder.getDependencies();
458
+
459
+ expect(deps).toEqual([]);
460
+ });
461
+ });
462
+
463
+ describe('getName()', () => {
464
+ it('should return IntegrationBuilder', () => {
465
+ expect(integrationBuilder.getName()).toBe('IntegrationBuilder');
466
+ });
467
+ });
468
+
469
+ describe('Webhook Handler Configuration', () => {
470
+ it('should create webhook handler when webhooks enabled with boolean true', async () => {
471
+ const appDefinition = {
472
+ integrations: [
473
+ {
474
+ Definition: {
475
+ name: 'hubspot',
476
+ webhooks: true,
477
+ }
478
+ },
479
+ ],
480
+ };
481
+
482
+ const result = await integrationBuilder.build(appDefinition, {});
483
+
484
+ expect(result.functions.hubspotWebhook).toBeDefined();
485
+ expect(result.functions.hubspotWebhook.handler).toBe(
486
+ 'node_modules/@friggframework/core/handlers/routers/integration-webhook-routers.handlers.hubspotWebhook.handler'
487
+ );
488
+ });
489
+
490
+ it('should create webhook handler when webhooks enabled with object', async () => {
491
+ const appDefinition = {
492
+ integrations: [
493
+ {
494
+ Definition: {
495
+ name: 'salesforce',
496
+ webhooks: { enabled: true },
497
+ }
498
+ },
499
+ ],
500
+ };
501
+
502
+ const result = await integrationBuilder.build(appDefinition, {});
503
+
504
+ expect(result.functions.salesforceWebhook).toBeDefined();
505
+ });
506
+
507
+ it('should NOT create webhook handler when webhooks disabled', async () => {
508
+ const appDefinition = {
509
+ integrations: [
510
+ {
511
+ Definition: {
512
+ name: 'slack',
513
+ webhooks: false,
514
+ }
515
+ },
516
+ ],
517
+ };
518
+
519
+ const result = await integrationBuilder.build(appDefinition, {});
520
+
521
+ expect(result.functions.slackWebhook).toBeUndefined();
522
+ });
523
+
524
+ it('should NOT create webhook handler when webhooks explicitly disabled in object', async () => {
525
+ const appDefinition = {
526
+ integrations: [
527
+ {
528
+ Definition: {
529
+ name: 'test',
530
+ webhooks: { enabled: false },
531
+ }
532
+ },
533
+ ],
534
+ };
535
+
536
+ const result = await integrationBuilder.build(appDefinition, {});
537
+
538
+ expect(result.functions.testWebhook).toBeUndefined();
539
+ });
540
+
541
+ it('should configure webhook with both base and ID-specific routes', async () => {
542
+ const appDefinition = {
543
+ integrations: [
544
+ {
545
+ Definition: {
546
+ name: 'stripe',
547
+ webhooks: true,
548
+ }
549
+ },
550
+ ],
551
+ };
552
+
553
+ const result = await integrationBuilder.build(appDefinition, {});
554
+
555
+ expect(result.functions.stripeWebhook.events).toEqual([
556
+ {
557
+ httpApi: {
558
+ path: '/api/stripe-integration/webhooks',
559
+ method: 'POST',
560
+ },
561
+ },
562
+ {
563
+ httpApi: {
564
+ path: '/api/stripe-integration/webhooks/{integrationId}',
565
+ method: 'POST',
566
+ },
567
+ },
568
+ ]);
569
+ });
570
+
571
+ it('should define webhook handler BEFORE catch-all proxy route (ordering bug fix)', async () => {
572
+ const appDefinition = {
573
+ integrations: [
574
+ {
575
+ Definition: {
576
+ name: 'asana',
577
+ webhooks: true,
578
+ }
579
+ },
580
+ ],
581
+ };
582
+
583
+ const result = await integrationBuilder.build(appDefinition, {});
584
+
585
+ // Get the keys (function names) in the order they were added
586
+ const functionKeys = Object.keys(result.functions);
587
+
588
+ // Webhook handler should be defined before the main integration handler
589
+ const webhookIndex = functionKeys.indexOf('asanaWebhook');
590
+ const integrationIndex = functionKeys.indexOf('asana');
591
+
592
+ expect(webhookIndex).toBeGreaterThanOrEqual(0);
593
+ expect(integrationIndex).toBeGreaterThan(0);
594
+ expect(webhookIndex).toBeLessThan(integrationIndex);
595
+ });
596
+
597
+ it('should maintain correct function order: webhook, integration, queue worker', async () => {
598
+ const appDefinition = {
599
+ integrations: [
600
+ {
601
+ Definition: {
602
+ name: 'test',
603
+ webhooks: true,
604
+ }
605
+ },
606
+ ],
607
+ };
608
+
609
+ const result = await integrationBuilder.build(appDefinition, {});
610
+
611
+ const functionKeys = Object.keys(result.functions);
612
+
613
+ // Expected order: dlqProcessor (from InternalErrorQueue), webhook, integration, queueWorker
614
+ expect(functionKeys).toEqual([
615
+ 'dlqProcessor',
616
+ 'testWebhook',
617
+ 'test',
618
+ 'testQueueWorker',
619
+ ]);
620
+ });
621
+
622
+ it('should handle multiple integrations with mixed webhook configurations', async () => {
623
+ const appDefinition = {
624
+ integrations: [
625
+ {
626
+ Definition: {
627
+ name: 'hubspot',
628
+ webhooks: true,
629
+ }
630
+ },
631
+ {
632
+ Definition: {
633
+ name: 'salesforce',
634
+ webhooks: false,
635
+ }
636
+ },
637
+ {
638
+ Definition: {
639
+ name: 'slack',
640
+ webhooks: { enabled: true },
641
+ }
642
+ },
643
+ ],
644
+ };
645
+
646
+ const result = await integrationBuilder.build(appDefinition, {});
647
+
648
+ // Hubspot: webhook enabled
649
+ expect(result.functions.hubspotWebhook).toBeDefined();
650
+ expect(result.functions.hubspot).toBeDefined();
651
+ expect(result.functions.hubspotQueueWorker).toBeDefined();
652
+
653
+ // Salesforce: webhook disabled
654
+ expect(result.functions.salesforceWebhook).toBeUndefined();
655
+ expect(result.functions.salesforce).toBeDefined();
656
+ expect(result.functions.salesforceQueueWorker).toBeDefined();
657
+
658
+ // Slack: webhook enabled via object
659
+ expect(result.functions.slackWebhook).toBeDefined();
660
+ expect(result.functions.slack).toBeDefined();
661
+ expect(result.functions.slackQueueWorker).toBeDefined();
662
+ });
663
+
664
+ it('should use skipEsbuild for webhook handlers', async () => {
665
+ const appDefinition = {
666
+ integrations: [
667
+ {
668
+ Definition: {
669
+ name: 'test',
670
+ webhooks: true,
671
+ }
672
+ },
673
+ ],
674
+ };
675
+
676
+ const result = await integrationBuilder.build(appDefinition, {});
677
+
678
+ expect(result.functions.testWebhook.skipEsbuild).toBe(true);
679
+ });
680
+
681
+ it('should apply package configuration to webhook handlers', async () => {
682
+ const appDefinition = {
683
+ integrations: [
684
+ {
685
+ Definition: {
686
+ name: 'test',
687
+ webhooks: true,
688
+ }
689
+ },
690
+ ],
691
+ };
692
+
693
+ const result = await integrationBuilder.build(appDefinition, {});
694
+
695
+ expect(result.functions.testWebhook.package).toBeDefined();
696
+ expect(result.functions.testWebhook.package.exclude).toContain('node_modules/aws-sdk/**');
697
+ expect(result.functions.testWebhook.package.exclude).toContain('node_modules/@prisma/**');
698
+ });
699
+ });
700
+
701
+ describe('Prisma Layer Configuration', () => {
702
+ it('should attach Prisma Lambda layer to queue worker functions', async () => {
703
+ const appDefinition = {
704
+ integrations: [
705
+ { Definition: { name: 'hubspot' } },
706
+ ],
707
+ };
708
+
709
+ const result = await integrationBuilder.build(appDefinition, {});
710
+
711
+ // Queue workers need Prisma layer for database operations
712
+ expect(result.functions.hubspotQueueWorker.layers).toEqual([
713
+ { Ref: 'PrismaLambdaLayer' }
714
+ ]);
715
+ });
716
+
717
+ it('should attach Prisma layer to multiple queue workers', async () => {
718
+ const appDefinition = {
719
+ integrations: [
720
+ { Definition: { name: 'hubspot' } },
721
+ { Definition: { name: 'salesforce' } },
722
+ { Definition: { name: 'slack' } },
723
+ ],
724
+ };
725
+
726
+ const result = await integrationBuilder.build(appDefinition, {});
727
+
728
+ expect(result.functions.hubspotQueueWorker.layers).toEqual([
729
+ { Ref: 'PrismaLambdaLayer' }
730
+ ]);
731
+ expect(result.functions.salesforceQueueWorker.layers).toEqual([
732
+ { Ref: 'PrismaLambdaLayer' }
733
+ ]);
734
+ expect(result.functions.slackQueueWorker.layers).toEqual([
735
+ { Ref: 'PrismaLambdaLayer' }
736
+ ]);
737
+ });
738
+
739
+ it('should attach Prisma layer to HTTP handlers for database access', async () => {
740
+ const appDefinition = {
741
+ integrations: [
742
+ { Definition: { name: 'stripe' } },
743
+ ],
744
+ };
745
+
746
+ const result = await integrationBuilder.build(appDefinition, {});
747
+
748
+ // HTTP handlers also need Prisma for integration queries
749
+ expect(result.functions.stripe.layers).toEqual([
750
+ { Ref: 'PrismaLambdaLayer' }
751
+ ]);
752
+ });
753
+
754
+ it('should attach Prisma layer to webhook handlers', async () => {
755
+ const appDefinition = {
756
+ integrations: [
757
+ {
758
+ Definition: {
759
+ name: 'hubspot',
760
+ webhooks: true,
761
+ }
762
+ },
763
+ ],
764
+ };
765
+
766
+ const result = await integrationBuilder.build(appDefinition, {});
767
+
768
+ // Webhook handlers need Prisma for credential lookups
769
+ expect(result.functions.hubspotWebhook.layers).toEqual([
770
+ { Ref: 'PrismaLambdaLayer' }
771
+ ]);
772
+ });
773
+
774
+ it('should not attach Prisma layer when usePrismaLambdaLayer=false', async () => {
775
+ const appDefinition = {
776
+ usePrismaLambdaLayer: false,
777
+ integrations: [
778
+ {
779
+ Definition: {
780
+ name: 'asana',
781
+ webhooks: true,
782
+ },
783
+ },
784
+ ],
785
+ };
786
+
787
+ const result = await integrationBuilder.build(appDefinition, {});
788
+
789
+ expect(result.functions.asana.layers).toBeUndefined();
790
+ expect(result.functions.asanaQueueWorker.layers).toBeUndefined();
791
+ expect(result.functions.asanaWebhook.layers).toBeUndefined();
792
+ expect(result.functions.asana.package.exclude).not.toEqual(
793
+ expect.arrayContaining(['node_modules/@prisma/**'])
794
+ );
795
+ });
796
+ });
797
+ });
798
+