@friggframework/devtools 2.0.0-next.45 → 2.0.0-next.46
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/infrastructure/ARCHITECTURE.md +487 -0
- package/infrastructure/HEALTH.md +468 -0
- package/infrastructure/README.md +51 -0
- package/infrastructure/__tests__/postgres-config.test.js +914 -0
- package/infrastructure/__tests__/template-generation.test.js +687 -0
- package/infrastructure/create-frigg-infrastructure.js +1 -1
- package/infrastructure/docs/POSTGRES-CONFIGURATION.md +630 -0
- package/infrastructure/{DEPLOYMENT-INSTRUCTIONS.md → docs/deployment-instructions.md} +3 -3
- package/infrastructure/{IAM-POLICY-TEMPLATES.md → docs/iam-policy-templates.md} +9 -10
- package/infrastructure/domains/database/aurora-builder.js +809 -0
- package/infrastructure/domains/database/aurora-builder.test.js +950 -0
- package/infrastructure/domains/database/aurora-discovery.js +87 -0
- package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
- package/infrastructure/domains/database/aurora-resolver.js +210 -0
- package/infrastructure/domains/database/aurora-resolver.test.js +347 -0
- package/infrastructure/domains/database/migration-builder.js +633 -0
- package/infrastructure/domains/database/migration-builder.test.js +294 -0
- package/infrastructure/domains/database/migration-resolver.js +163 -0
- package/infrastructure/domains/database/migration-resolver.test.js +337 -0
- package/infrastructure/domains/health/application/ports/IPropertyReconciler.js +164 -0
- package/infrastructure/domains/health/application/ports/IResourceDetector.js +129 -0
- package/infrastructure/domains/health/application/ports/IResourceImporter.js +142 -0
- package/infrastructure/domains/health/application/ports/IStackRepository.js +131 -0
- package/infrastructure/domains/health/application/ports/index.js +26 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/execute-resource-import-use-case.test.js +679 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/mismatch-analyzer-method-name.test.js +167 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +1130 -0
- package/infrastructure/domains/health/application/use-cases/execute-resource-import-use-case.js +221 -0
- package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +152 -0
- package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.test.js +343 -0
- package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +535 -0
- package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.test.js +376 -0
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +213 -0
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +441 -0
- package/infrastructure/domains/health/docs/ACME-DEV-DRIFT-ANALYSIS.md +267 -0
- package/infrastructure/domains/health/docs/BUILD-VS-DEPLOYED-TEMPLATE-ANALYSIS.md +324 -0
- package/infrastructure/domains/health/docs/ORPHAN-DETECTION-ANALYSIS.md +386 -0
- package/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md +1419 -0
- package/infrastructure/domains/health/docs/TDD-IMPLEMENTATION-SUMMARY.md +391 -0
- package/infrastructure/domains/health/docs/TEMPLATE-COMPARISON-IMPLEMENTATION.md +551 -0
- package/infrastructure/domains/health/domain/entities/issue.js +299 -0
- package/infrastructure/domains/health/domain/entities/issue.test.js +528 -0
- package/infrastructure/domains/health/domain/entities/property-mismatch.js +108 -0
- package/infrastructure/domains/health/domain/entities/property-mismatch.test.js +275 -0
- package/infrastructure/domains/health/domain/entities/resource.js +159 -0
- package/infrastructure/domains/health/domain/entities/resource.test.js +432 -0
- package/infrastructure/domains/health/domain/entities/stack-health-report.js +306 -0
- package/infrastructure/domains/health/domain/entities/stack-health-report.test.js +601 -0
- package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +380 -0
- package/infrastructure/domains/health/domain/services/__tests__/import-progress-monitor.test.js +971 -0
- package/infrastructure/domains/health/domain/services/__tests__/import-template-generator.test.js +1150 -0
- package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +672 -0
- package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +496 -0
- package/infrastructure/domains/health/domain/services/__tests__/update-progress-monitor.test.js +419 -0
- package/infrastructure/domains/health/domain/services/health-score-calculator.js +248 -0
- package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +504 -0
- package/infrastructure/domains/health/domain/services/import-progress-monitor.js +195 -0
- package/infrastructure/domains/health/domain/services/import-template-generator.js +435 -0
- package/infrastructure/domains/health/domain/services/logical-id-mapper.js +345 -0
- package/infrastructure/domains/health/domain/services/mismatch-analyzer.js +234 -0
- package/infrastructure/domains/health/domain/services/mismatch-analyzer.test.js +431 -0
- package/infrastructure/domains/health/domain/services/property-mutability-config.js +382 -0
- package/infrastructure/domains/health/domain/services/template-parser.js +245 -0
- package/infrastructure/domains/health/domain/services/update-progress-monitor.js +192 -0
- package/infrastructure/domains/health/domain/value-objects/health-score.js +138 -0
- package/infrastructure/domains/health/domain/value-objects/health-score.test.js +267 -0
- package/infrastructure/domains/health/domain/value-objects/property-mutability.js +161 -0
- package/infrastructure/domains/health/domain/value-objects/property-mutability.test.js +198 -0
- package/infrastructure/domains/health/domain/value-objects/resource-state.js +167 -0
- package/infrastructure/domains/health/domain/value-objects/resource-state.test.js +196 -0
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.js +192 -0
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.test.js +262 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-cfn-tagged.test.js +312 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-multi-stack.test.js +367 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-relationship-analysis.test.js +432 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.js +784 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +1133 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +565 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +554 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.js +318 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.test.js +398 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +777 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.test.js +580 -0
- package/infrastructure/domains/integration/integration-builder.js +397 -0
- package/infrastructure/domains/integration/integration-builder.test.js +593 -0
- package/infrastructure/domains/integration/integration-resolver.js +170 -0
- package/infrastructure/domains/integration/integration-resolver.test.js +369 -0
- package/infrastructure/domains/integration/websocket-builder.js +69 -0
- package/infrastructure/domains/integration/websocket-builder.test.js +195 -0
- package/infrastructure/domains/networking/vpc-builder.js +1829 -0
- package/infrastructure/domains/networking/vpc-builder.test.js +1262 -0
- package/infrastructure/domains/networking/vpc-discovery.js +177 -0
- package/infrastructure/domains/networking/vpc-discovery.test.js +350 -0
- package/infrastructure/domains/networking/vpc-resolver.js +324 -0
- package/infrastructure/domains/networking/vpc-resolver.test.js +501 -0
- package/infrastructure/domains/parameters/ssm-builder.js +79 -0
- package/infrastructure/domains/parameters/ssm-builder.test.js +189 -0
- package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
- package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
- package/infrastructure/{iam-generator.js → domains/security/iam-generator.js} +2 -2
- package/infrastructure/domains/security/kms-builder.js +366 -0
- package/infrastructure/domains/security/kms-builder.test.js +374 -0
- package/infrastructure/domains/security/kms-discovery.js +80 -0
- package/infrastructure/domains/security/kms-discovery.test.js +177 -0
- package/infrastructure/domains/security/kms-resolver.js +96 -0
- package/infrastructure/domains/security/kms-resolver.test.js +216 -0
- package/infrastructure/domains/shared/base-builder.js +112 -0
- package/infrastructure/domains/shared/base-resolver.js +186 -0
- package/infrastructure/domains/shared/base-resolver.test.js +305 -0
- package/infrastructure/domains/shared/builder-orchestrator.js +212 -0
- package/infrastructure/domains/shared/builder-orchestrator.test.js +213 -0
- package/infrastructure/domains/shared/cloudformation-discovery-v2.js +334 -0
- package/infrastructure/domains/shared/cloudformation-discovery.js +375 -0
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +590 -0
- package/infrastructure/domains/shared/environment-builder.js +119 -0
- package/infrastructure/domains/shared/environment-builder.test.js +247 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.js +544 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +377 -0
- package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
- package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
- package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
- package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
- package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
- package/infrastructure/domains/shared/resource-discovery.js +192 -0
- package/infrastructure/domains/shared/resource-discovery.test.js +552 -0
- package/infrastructure/domains/shared/types/app-definition.js +205 -0
- package/infrastructure/domains/shared/types/discovery-result.js +106 -0
- package/infrastructure/domains/shared/types/discovery-result.test.js +258 -0
- package/infrastructure/domains/shared/types/index.js +46 -0
- package/infrastructure/domains/shared/types/resource-ownership.js +108 -0
- package/infrastructure/domains/shared/types/resource-ownership.test.js +101 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js +380 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +248 -0
- package/infrastructure/domains/shared/utilities/handler-path-resolver.js +134 -0
- package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +268 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +55 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +138 -0
- package/infrastructure/{env-validator.js → domains/shared/validation/env-validator.js} +2 -1
- package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
- package/infrastructure/esbuild.config.js +53 -0
- package/infrastructure/infrastructure-composer.js +87 -0
- package/infrastructure/{serverless-template.test.js → infrastructure-composer.test.js} +115 -24
- package/infrastructure/scripts/build-prisma-layer.js +553 -0
- package/infrastructure/scripts/build-prisma-layer.test.js +102 -0
- package/infrastructure/{build-time-discovery.js → scripts/build-time-discovery.js} +80 -48
- package/infrastructure/{build-time-discovery.test.js → scripts/build-time-discovery.test.js} +5 -4
- package/layers/prisma/nodejs/package.json +8 -0
- package/management-ui/server/utils/cliIntegration.js +1 -1
- package/management-ui/server/utils/environment/awsParameterStore.js +29 -18
- package/package.json +11 -11
- package/frigg-cli/.eslintrc.js +0 -141
- package/frigg-cli/__tests__/unit/commands/build.test.js +0 -251
- package/frigg-cli/__tests__/unit/commands/db-setup.test.js +0 -548
- package/frigg-cli/__tests__/unit/commands/install.test.js +0 -400
- package/frigg-cli/__tests__/unit/commands/ui.test.js +0 -346
- package/frigg-cli/__tests__/unit/utils/database-validator.test.js +0 -366
- package/frigg-cli/__tests__/unit/utils/error-messages.test.js +0 -304
- package/frigg-cli/__tests__/unit/utils/prisma-runner.test.js +0 -486
- package/frigg-cli/__tests__/utils/mock-factory.js +0 -270
- package/frigg-cli/__tests__/utils/prisma-mock.js +0 -194
- package/frigg-cli/__tests__/utils/test-fixtures.js +0 -463
- package/frigg-cli/__tests__/utils/test-setup.js +0 -287
- package/frigg-cli/build-command/index.js +0 -65
- package/frigg-cli/db-setup-command/index.js +0 -193
- package/frigg-cli/deploy-command/index.js +0 -175
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +0 -301
- package/frigg-cli/generate-command/azure-generator.js +0 -43
- package/frigg-cli/generate-command/gcp-generator.js +0 -47
- package/frigg-cli/generate-command/index.js +0 -332
- package/frigg-cli/generate-command/terraform-generator.js +0 -555
- package/frigg-cli/generate-iam-command.js +0 -118
- package/frigg-cli/index.js +0 -75
- package/frigg-cli/index.test.js +0 -158
- package/frigg-cli/init-command/backend-first-handler.js +0 -756
- package/frigg-cli/init-command/index.js +0 -93
- package/frigg-cli/init-command/template-handler.js +0 -143
- package/frigg-cli/install-command/backend-js.js +0 -33
- package/frigg-cli/install-command/commit-changes.js +0 -16
- package/frigg-cli/install-command/environment-variables.js +0 -127
- package/frigg-cli/install-command/environment-variables.test.js +0 -136
- package/frigg-cli/install-command/index.js +0 -54
- package/frigg-cli/install-command/install-package.js +0 -13
- package/frigg-cli/install-command/integration-file.js +0 -30
- package/frigg-cli/install-command/logger.js +0 -12
- package/frigg-cli/install-command/template.js +0 -90
- package/frigg-cli/install-command/validate-package.js +0 -75
- package/frigg-cli/jest.config.js +0 -124
- package/frigg-cli/package.json +0 -54
- package/frigg-cli/start-command/index.js +0 -149
- package/frigg-cli/start-command/start-command.test.js +0 -297
- package/frigg-cli/test/init-command.test.js +0 -180
- package/frigg-cli/test/npm-registry.test.js +0 -319
- package/frigg-cli/ui-command/index.js +0 -154
- package/frigg-cli/utils/app-resolver.js +0 -319
- package/frigg-cli/utils/backend-path.js +0 -25
- package/frigg-cli/utils/database-validator.js +0 -161
- package/frigg-cli/utils/error-messages.js +0 -257
- package/frigg-cli/utils/npm-registry.js +0 -167
- package/frigg-cli/utils/prisma-runner.js +0 -280
- package/frigg-cli/utils/process-manager.js +0 -199
- package/frigg-cli/utils/repo-detection.js +0 -405
- package/infrastructure/aws-discovery.js +0 -1176
- package/infrastructure/aws-discovery.test.js +0 -1220
- package/infrastructure/serverless-template.js +0 -2094
- /package/infrastructure/{WEBSOCKET-CONFIGURATION.md → docs/WEBSOCKET-CONFIGURATION.md} +0 -0
- /package/infrastructure/{GENERATE-IAM-DOCS.md → docs/generate-iam-command.md} +0 -0
- /package/infrastructure/{iam-generator.test.js → domains/security/iam-generator.test.js} +0 -0
- /package/infrastructure/{frigg-deployment-iam-stack.yaml → domains/security/templates/frigg-deployment-iam-stack.yaml} +0 -0
- /package/infrastructure/{iam-policy-basic.json → domains/security/templates/iam-policy-basic.json} +0 -0
- /package/infrastructure/{iam-policy-full.json → domains/security/templates/iam-policy-full.json} +0 -0
- /package/infrastructure/{run-discovery.js → scripts/run-discovery.js} +0 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for KMS Builder
|
|
3
|
+
*
|
|
4
|
+
* Tests KMS key creation and configuration
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { KmsBuilder } = require('./kms-builder');
|
|
8
|
+
const { ValidationResult } = require('../shared/base-builder');
|
|
9
|
+
|
|
10
|
+
describe('KmsBuilder', () => {
|
|
11
|
+
let kmsBuilder;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
kmsBuilder = new KmsBuilder();
|
|
15
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('shouldExecute()', () => {
|
|
23
|
+
it('should return true when encryption method is kms', () => {
|
|
24
|
+
const appDefinition = {
|
|
25
|
+
encryption: {
|
|
26
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
expect(kmsBuilder.shouldExecute(appDefinition)).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should return false when encryption method is aes', () => {
|
|
34
|
+
const appDefinition = {
|
|
35
|
+
encryption: {
|
|
36
|
+
fieldLevelEncryptionMethod: 'aes',
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
expect(kmsBuilder.shouldExecute(appDefinition)).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should return false when encryption is not defined', () => {
|
|
44
|
+
const appDefinition = {};
|
|
45
|
+
|
|
46
|
+
expect(kmsBuilder.shouldExecute(appDefinition)).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should return false when fieldLevelEncryptionMethod is not defined', () => {
|
|
50
|
+
const appDefinition = {
|
|
51
|
+
encryption: {},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
expect(kmsBuilder.shouldExecute(appDefinition)).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should return false when FRIGG_SKIP_AWS_DISCOVERY is set (local mode)', () => {
|
|
58
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
59
|
+
const appDefinition = {
|
|
60
|
+
encryption: {
|
|
61
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
expect(kmsBuilder.shouldExecute(appDefinition)).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('validate()', () => {
|
|
70
|
+
it('should pass validation for valid KMS config', () => {
|
|
71
|
+
const appDefinition = {
|
|
72
|
+
encryption: {
|
|
73
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
74
|
+
createResourceIfNoneFound: true,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const result = kmsBuilder.validate(appDefinition);
|
|
79
|
+
|
|
80
|
+
expect(result).toBeInstanceOf(ValidationResult);
|
|
81
|
+
expect(result.valid).toBe(true);
|
|
82
|
+
expect(result.errors).toEqual([]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should pass validation when createResourceIfNoneFound is boolean', () => {
|
|
86
|
+
const appDefinition = {
|
|
87
|
+
encryption: {
|
|
88
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
89
|
+
createResourceIfNoneFound: false,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const result = kmsBuilder.validate(appDefinition);
|
|
94
|
+
|
|
95
|
+
expect(result.valid).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should error if encryption configuration is missing', () => {
|
|
99
|
+
const appDefinition = {};
|
|
100
|
+
|
|
101
|
+
const result = kmsBuilder.validate(appDefinition);
|
|
102
|
+
|
|
103
|
+
expect(result.valid).toBe(false);
|
|
104
|
+
expect(result.errors).toContain('Encryption configuration is missing');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should pass when encryption method is not kms', () => {
|
|
108
|
+
const appDefinition = {
|
|
109
|
+
encryption: {
|
|
110
|
+
fieldLevelEncryptionMethod: 'aes',
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const result = kmsBuilder.validate(appDefinition);
|
|
115
|
+
|
|
116
|
+
expect(result.valid).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should error when createResourceIfNoneFound is not boolean', () => {
|
|
120
|
+
const appDefinition = {
|
|
121
|
+
encryption: {
|
|
122
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
123
|
+
createResourceIfNoneFound: 'yes',
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const result = kmsBuilder.validate(appDefinition);
|
|
128
|
+
|
|
129
|
+
expect(result.valid).toBe(false);
|
|
130
|
+
expect(result.errors).toContain(
|
|
131
|
+
'encryption.createResourceIfNoneFound must be a boolean'
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('build() - with discovered key', () => {
|
|
137
|
+
it('should use discovered KMS key', async () => {
|
|
138
|
+
const appDefinition = {
|
|
139
|
+
encryption: {
|
|
140
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const discoveredResources = {
|
|
145
|
+
defaultKmsKeyId: 'arn:aws:kms:us-east-1:123456:key/abc-123',
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const result = await kmsBuilder.build(appDefinition, discoveredResources);
|
|
149
|
+
|
|
150
|
+
expect(result.environment.KMS_KEY_ARN).toBe('arn:aws:kms:us-east-1:123456:key/abc-123');
|
|
151
|
+
expect(result.pluginConfig.kmsGrants).toBeUndefined();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should add IAM permissions for KMS operations', async () => {
|
|
155
|
+
const appDefinition = {
|
|
156
|
+
encryption: {
|
|
157
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const discoveredResources = {
|
|
162
|
+
defaultKmsKeyId: 'arn:aws:kms:us-east-1:123456:key/abc',
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const result = await kmsBuilder.build(appDefinition, discoveredResources);
|
|
166
|
+
|
|
167
|
+
expect(result.iamStatements).toHaveLength(1);
|
|
168
|
+
expect(result.iamStatements[0]).toEqual({
|
|
169
|
+
Effect: 'Allow',
|
|
170
|
+
Action: ['kms:GenerateDataKey', 'kms:Decrypt', 'kms:Encrypt', 'kms:DescribeKey'],
|
|
171
|
+
Resource: 'arn:aws:kms:us-east-1:123456:key/abc',
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should NOT use serverless-kms-grants plugin (deprecated)', async () => {
|
|
176
|
+
const appDefinition = {
|
|
177
|
+
encryption: {
|
|
178
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const discoveredResources = {
|
|
183
|
+
defaultKmsKeyId: 'arn:aws:kms:us-east-1:123456:key/abc',
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const result = await kmsBuilder.build(appDefinition, discoveredResources);
|
|
187
|
+
|
|
188
|
+
expect(result.plugins).not.toContain('serverless-kms-grants');
|
|
189
|
+
expect(result.pluginConfig.kmsGrants).toBeUndefined();
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('build() - create new key', () => {
|
|
194
|
+
it('should create new KMS key when none found and createResourceIfNoneFound is true', async () => {
|
|
195
|
+
const appDefinition = {
|
|
196
|
+
encryption: {
|
|
197
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
198
|
+
createResourceIfNoneFound: true,
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const discoveredResources = {
|
|
203
|
+
defaultKmsKeyId: null,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const result = await kmsBuilder.build(appDefinition, discoveredResources);
|
|
207
|
+
|
|
208
|
+
expect(result.resources.FriggKMSKey).toBeDefined();
|
|
209
|
+
expect(result.resources.FriggKMSKey.Type).toBe('AWS::KMS::Key');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should create KMS key alias', async () => {
|
|
213
|
+
const appDefinition = {
|
|
214
|
+
encryption: {
|
|
215
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
216
|
+
createResourceIfNoneFound: true,
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const discoveredResources = {};
|
|
221
|
+
|
|
222
|
+
const result = await kmsBuilder.build(appDefinition, discoveredResources);
|
|
223
|
+
|
|
224
|
+
expect(result.resources.FriggKMSKeyAlias).toBeDefined();
|
|
225
|
+
expect(result.resources.FriggKMSKeyAlias.Type).toBe('AWS::KMS::Alias');
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('should enable key rotation for new keys', async () => {
|
|
229
|
+
const appDefinition = {
|
|
230
|
+
encryption: {
|
|
231
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
232
|
+
createResourceIfNoneFound: true,
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const result = await kmsBuilder.build(appDefinition, {});
|
|
237
|
+
|
|
238
|
+
expect(result.resources.FriggKMSKey.Properties.EnableKeyRotation).toBe(true);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should use CloudFormation reference for new key', async () => {
|
|
242
|
+
const appDefinition = {
|
|
243
|
+
encryption: {
|
|
244
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
245
|
+
createResourceIfNoneFound: true,
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const result = await kmsBuilder.build(appDefinition, {});
|
|
250
|
+
|
|
251
|
+
expect(result.environment.KMS_KEY_ARN).toEqual({
|
|
252
|
+
'Fn::GetAtt': ['FriggKMSKey', 'Arn'],
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('should set DeletionPolicy to Retain for key resources', async () => {
|
|
257
|
+
const appDefinition = {
|
|
258
|
+
encryption: {
|
|
259
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
260
|
+
createResourceIfNoneFound: true,
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const result = await kmsBuilder.build(appDefinition, {});
|
|
265
|
+
|
|
266
|
+
expect(result.resources.FriggKMSKey.DeletionPolicy).toBe('Retain');
|
|
267
|
+
expect(result.resources.FriggKMSKey.UpdateReplacePolicy).toBe('Retain');
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
describe('getDependencies()', () => {
|
|
272
|
+
it('should have no dependencies', () => {
|
|
273
|
+
const deps = kmsBuilder.getDependencies();
|
|
274
|
+
|
|
275
|
+
expect(deps).toEqual([]);
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
describe('getName()', () => {
|
|
280
|
+
it('should return KmsBuilder', () => {
|
|
281
|
+
expect(kmsBuilder.getName()).toBe('KmsBuilder');
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe('Key policies', () => {
|
|
286
|
+
it('should create key policy allowing root account admin', async () => {
|
|
287
|
+
const appDefinition = {
|
|
288
|
+
encryption: {
|
|
289
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
290
|
+
createResourceIfNoneFound: true,
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const result = await kmsBuilder.build(appDefinition, {});
|
|
295
|
+
|
|
296
|
+
const policy = result.resources.FriggKMSKey.Properties.KeyPolicy;
|
|
297
|
+
const rootStatement = policy.Statement.find(s => s.Sid === 'AllowRootAccountAdmin');
|
|
298
|
+
|
|
299
|
+
expect(rootStatement).toBeDefined();
|
|
300
|
+
expect(rootStatement.Action).toBe('kms:*');
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('should create key policy allowing Lambda service', async () => {
|
|
304
|
+
const appDefinition = {
|
|
305
|
+
encryption: {
|
|
306
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
307
|
+
createResourceIfNoneFound: true,
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const result = await kmsBuilder.build(appDefinition, {});
|
|
312
|
+
|
|
313
|
+
const policy = result.resources.FriggKMSKey.Properties.KeyPolicy;
|
|
314
|
+
const lambdaStatement = policy.Statement.find(s => s.Sid === 'AllowLambdaService');
|
|
315
|
+
|
|
316
|
+
expect(lambdaStatement).toBeDefined();
|
|
317
|
+
expect(lambdaStatement.Action).toContain('kms:GenerateDataKey');
|
|
318
|
+
expect(lambdaStatement.Action).toContain('kms:Decrypt');
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('should create key policy allowing Lambda execution role direct access', async () => {
|
|
322
|
+
const appDefinition = {
|
|
323
|
+
encryption: {
|
|
324
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
325
|
+
createResourceIfNoneFound: true,
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const result = await kmsBuilder.build(appDefinition, {});
|
|
330
|
+
|
|
331
|
+
const policy = result.resources.FriggKMSKey.Properties.KeyPolicy;
|
|
332
|
+
// Should NOT have AllowLambdaExecutionRole statement to avoid circular dependency
|
|
333
|
+
// (KMS Key → IAM Role → KMS Key = circular)
|
|
334
|
+
// IAM policies already grant KMS permissions, so key policy doesn't need to reference the role
|
|
335
|
+
const roleStatement = policy.Statement.find(s => s.Sid === 'AllowLambdaExecutionRole');
|
|
336
|
+
expect(roleStatement).toBeUndefined();
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
describe('Error handling', () => {
|
|
341
|
+
it('should fallback to environment variable when no key discovered and createResourceIfNoneFound is false', async () => {
|
|
342
|
+
const appDefinition = {
|
|
343
|
+
encryption: {
|
|
344
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
345
|
+
createResourceIfNoneFound: false,
|
|
346
|
+
},
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const discoveredResources = {
|
|
350
|
+
defaultKmsKeyId: null,
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const result = await kmsBuilder.build(appDefinition, discoveredResources);
|
|
354
|
+
|
|
355
|
+
expect(result.environment.KMS_KEY_ARN).toBe('${env:AWS_DISCOVERY_KMS_KEY_ID}');
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('should fallback to environment variable when createResourceIfNoneFound not specified', async () => {
|
|
359
|
+
const appDefinition = {
|
|
360
|
+
encryption: {
|
|
361
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const discoveredResources = {};
|
|
366
|
+
|
|
367
|
+
const result = await kmsBuilder.build(appDefinition, discoveredResources);
|
|
368
|
+
|
|
369
|
+
// Should format env var as ARN for IAM policies
|
|
370
|
+
expect(result.environment.KMS_KEY_ARN).toBe('arn:aws:kms:${self:provider.region}:${aws:accountId}:key/${env:AWS_DISCOVERY_KMS_KEY_ID}');
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KMS Discovery Service
|
|
3
|
+
*
|
|
4
|
+
* Domain Service - Hexagonal Architecture
|
|
5
|
+
*
|
|
6
|
+
* Discovers KMS encryption keys using the cloud provider adapter.
|
|
7
|
+
* Adds domain-specific validation and key selection logic.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
class KmsDiscovery {
|
|
11
|
+
/**
|
|
12
|
+
* @param {CloudProviderAdapter} provider - Cloud provider adapter instance
|
|
13
|
+
*/
|
|
14
|
+
constructor(provider) {
|
|
15
|
+
this.provider = provider;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Discover KMS encryption keys
|
|
20
|
+
*
|
|
21
|
+
* @param {Object} config - Discovery configuration
|
|
22
|
+
* @param {string} [config.keyId] - Specific key ID to discover
|
|
23
|
+
* @param {string} [config.keyAlias] - Key alias to search for
|
|
24
|
+
* @param {string} [config.serviceName] - Service name for filtering
|
|
25
|
+
* @param {string} [config.stage] - Deployment stage
|
|
26
|
+
* @returns {Promise<Object>} Discovered KMS key resources
|
|
27
|
+
*/
|
|
28
|
+
async discover(config) {
|
|
29
|
+
console.log('🔍 Discovering KMS keys...');
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const rawResources = await this.provider.discoverKmsKeys(config);
|
|
33
|
+
|
|
34
|
+
const result = {
|
|
35
|
+
kmsKeyId: null,
|
|
36
|
+
kmsKeyArn: null,
|
|
37
|
+
kmsKeyAlias: null,
|
|
38
|
+
keys: rawResources.keys,
|
|
39
|
+
aliases: rawResources.aliases,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Use default key if found
|
|
43
|
+
if (rawResources.defaultKey) {
|
|
44
|
+
result.kmsKeyId = rawResources.defaultKey.Arn;
|
|
45
|
+
result.kmsKeyArn = rawResources.defaultKey.Arn;
|
|
46
|
+
result.defaultKmsKeyId = rawResources.defaultKey.Arn;
|
|
47
|
+
|
|
48
|
+
// Find alias for this key
|
|
49
|
+
const keyAlias = rawResources.aliases.find(
|
|
50
|
+
a => a.TargetKeyId === rawResources.defaultKey.KeyId
|
|
51
|
+
);
|
|
52
|
+
if (keyAlias) {
|
|
53
|
+
result.kmsKeyAlias = keyAlias.AliasName;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log(` ✓ Found KMS key: ${result.kmsKeyId}`);
|
|
57
|
+
if (result.kmsKeyAlias) {
|
|
58
|
+
console.log(` ✓ Key alias: ${result.kmsKeyAlias}`);
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
console.log(' ℹ No KMS key found');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return result;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(' ✗ KMS discovery failed:', error.message);
|
|
67
|
+
return {
|
|
68
|
+
kmsKeyId: null,
|
|
69
|
+
kmsKeyArn: null,
|
|
70
|
+
defaultKmsKeyId: null,
|
|
71
|
+
kmsKeyAlias: null,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = {
|
|
78
|
+
KmsDiscovery,
|
|
79
|
+
};
|
|
80
|
+
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for KMS Discovery Service
|
|
3
|
+
*
|
|
4
|
+
* Tests KMS encryption key discovery with mocked cloud provider
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { KmsDiscovery } = require('./kms-discovery');
|
|
8
|
+
|
|
9
|
+
describe('KmsDiscovery', () => {
|
|
10
|
+
let mockProvider;
|
|
11
|
+
let kmsDiscovery;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
mockProvider = {
|
|
15
|
+
discoverKmsKeys: jest.fn(),
|
|
16
|
+
getName: jest.fn().mockReturnValue('aws'),
|
|
17
|
+
};
|
|
18
|
+
kmsDiscovery = new KmsDiscovery(mockProvider);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('discover()', () => {
|
|
22
|
+
it('should delegate to provider and transform results', async () => {
|
|
23
|
+
const mockProviderResponse = {
|
|
24
|
+
keys: [
|
|
25
|
+
{
|
|
26
|
+
KeyId: 'key-123',
|
|
27
|
+
Arn: 'arn:aws:kms:us-east-1:123456:key/key-123',
|
|
28
|
+
Enabled: true,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
aliases: [
|
|
32
|
+
{
|
|
33
|
+
AliasName: 'alias/frigg-key',
|
|
34
|
+
TargetKeyId: 'key-123',
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
defaultKey: {
|
|
38
|
+
KeyId: 'key-123',
|
|
39
|
+
Arn: 'arn:aws:kms:us-east-1:123456:key/key-123',
|
|
40
|
+
Enabled: true,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
mockProvider.discoverKmsKeys.mockResolvedValue(mockProviderResponse);
|
|
45
|
+
|
|
46
|
+
const result = await kmsDiscovery.discover({});
|
|
47
|
+
|
|
48
|
+
expect(mockProvider.discoverKmsKeys).toHaveBeenCalledWith({});
|
|
49
|
+
expect(result.kmsKeyId).toBe('arn:aws:kms:us-east-1:123456:key/key-123');
|
|
50
|
+
expect(result.kmsKeyArn).toBe('arn:aws:kms:us-east-1:123456:key/key-123');
|
|
51
|
+
expect(result.defaultKmsKeyId).toBe('arn:aws:kms:us-east-1:123456:key/key-123');
|
|
52
|
+
expect(result.kmsKeyAlias).toBe('alias/frigg-key');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should handle no KMS keys found', async () => {
|
|
56
|
+
mockProvider.discoverKmsKeys.mockResolvedValue({
|
|
57
|
+
keys: [],
|
|
58
|
+
aliases: [],
|
|
59
|
+
defaultKey: null,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const result = await kmsDiscovery.discover({});
|
|
63
|
+
|
|
64
|
+
// When no keys found, properties are undefined (not explicitly set to null)
|
|
65
|
+
expect(result.kmsKeyId).toBeFalsy();
|
|
66
|
+
expect(result.kmsKeyArn).toBeFalsy();
|
|
67
|
+
expect(result.defaultKmsKeyId).toBeFalsy();
|
|
68
|
+
expect(result.kmsKeyAlias).toBeFalsy();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should handle KMS key without alias', async () => {
|
|
72
|
+
mockProvider.discoverKmsKeys.mockResolvedValue({
|
|
73
|
+
keys: [
|
|
74
|
+
{
|
|
75
|
+
KeyId: 'key-456',
|
|
76
|
+
Arn: 'arn:aws:kms:us-east-1:123456:key/key-456',
|
|
77
|
+
Enabled: true,
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
aliases: [],
|
|
81
|
+
defaultKey: {
|
|
82
|
+
KeyId: 'key-456',
|
|
83
|
+
Arn: 'arn:aws:kms:us-east-1:123456:key/key-456',
|
|
84
|
+
Enabled: true,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const result = await kmsDiscovery.discover({});
|
|
89
|
+
|
|
90
|
+
expect(result.kmsKeyId).toBe('arn:aws:kms:us-east-1:123456:key/key-456');
|
|
91
|
+
expect(result.kmsKeyAlias).toBeNull();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should pass config to provider', async () => {
|
|
95
|
+
mockProvider.discoverKmsKeys.mockResolvedValue({
|
|
96
|
+
keys: [],
|
|
97
|
+
aliases: [],
|
|
98
|
+
defaultKey: null,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const config = {
|
|
102
|
+
keyId: 'key-specific',
|
|
103
|
+
keyAlias: 'alias/custom',
|
|
104
|
+
serviceName: 'test-service',
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
await kmsDiscovery.discover(config);
|
|
108
|
+
|
|
109
|
+
expect(mockProvider.discoverKmsKeys).toHaveBeenCalledWith(config);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should handle discovery errors gracefully', async () => {
|
|
113
|
+
mockProvider.discoverKmsKeys.mockRejectedValue(new Error('KMS API Error'));
|
|
114
|
+
|
|
115
|
+
const result = await kmsDiscovery.discover({});
|
|
116
|
+
|
|
117
|
+
expect(result.kmsKeyId).toBeNull();
|
|
118
|
+
expect(result.kmsKeyArn).toBeNull();
|
|
119
|
+
expect(result.defaultKmsKeyId).toBeNull();
|
|
120
|
+
expect(result.kmsKeyAlias).toBeNull();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should find alias for discovered key', async () => {
|
|
124
|
+
mockProvider.discoverKmsKeys.mockResolvedValue({
|
|
125
|
+
keys: [
|
|
126
|
+
{
|
|
127
|
+
KeyId: 'key-789',
|
|
128
|
+
Arn: 'arn:aws:kms:eu-west-1:123456:key/key-789',
|
|
129
|
+
Enabled: true,
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
aliases: [
|
|
133
|
+
{
|
|
134
|
+
AliasName: 'alias/other-key',
|
|
135
|
+
TargetKeyId: 'key-999',
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
AliasName: 'alias/my-key',
|
|
139
|
+
TargetKeyId: 'key-789',
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
defaultKey: {
|
|
143
|
+
KeyId: 'key-789',
|
|
144
|
+
Arn: 'arn:aws:kms:eu-west-1:123456:key/key-789',
|
|
145
|
+
Enabled: true,
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const result = await kmsDiscovery.discover({});
|
|
150
|
+
|
|
151
|
+
expect(result.kmsKeyAlias).toBe('alias/my-key');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should return all keys and aliases for reference', async () => {
|
|
155
|
+
const mockKeys = [
|
|
156
|
+
{ KeyId: 'key-1', Arn: 'arn:1', Enabled: true },
|
|
157
|
+
{ KeyId: 'key-2', Arn: 'arn:2', Enabled: true },
|
|
158
|
+
];
|
|
159
|
+
const mockAliases = [
|
|
160
|
+
{ AliasName: 'alias/one', TargetKeyId: 'key-1' },
|
|
161
|
+
{ AliasName: 'alias/two', TargetKeyId: 'key-2' },
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
mockProvider.discoverKmsKeys.mockResolvedValue({
|
|
165
|
+
keys: mockKeys,
|
|
166
|
+
aliases: mockAliases,
|
|
167
|
+
defaultKey: mockKeys[0],
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const result = await kmsDiscovery.discover({});
|
|
171
|
+
|
|
172
|
+
expect(result.keys).toEqual(mockKeys);
|
|
173
|
+
expect(result.aliases).toEqual(mockAliases);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|