Skip to content

Commit a404c85

Browse files
committed
add lza config validate test
1 parent f6daea3 commit a404c85

File tree

1 file changed

+168
-0
lines changed

1 file changed

+168
-0
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import fs from 'fs';
2+
import os from 'os';
3+
import path from 'path';
4+
import { DescribeOrganizationCommand, ListRootsCommand, OrganizationsClient } from '@aws-sdk/client-organizations';
5+
import { GetParameterCommand, SSMClient } from '@aws-sdk/client-ssm';
6+
import { ListInstancesCommand, SSOAdminClient } from '@aws-sdk/client-sso-admin';
7+
import { GetCallerIdentityCommand, STSClient } from '@aws-sdk/client-sts';
8+
import { mockClient } from 'aws-sdk-client-mock';
9+
import { Cli } from 'clipanion';
10+
import { Init } from '../../src/commands/init';
11+
import { LzaConfigValidate } from '../../src/commands/lza-config-validate';
12+
import { AWS_ACCELERATOR_INSTALLER_STACK_VERSION_SSM_PARAMETER_NAME, LZA_SOURCE_PATH, loadConfigSync } from '../../src/config';
13+
import { getCheckoutPath } from '../../src/core/accelerator/repository/checkout';
14+
import * as execModule from '../../src/core/util/exec';
15+
16+
describe('LZA Config Validate command', () => {
17+
// Create mocks for AWS services (used during init rendering)
18+
const ssmMock = mockClient(SSMClient);
19+
const stsMock = mockClient(STSClient);
20+
const organizationsMock = mockClient(OrganizationsClient);
21+
const ssoAdminMock = mockClient(SSOAdminClient);
22+
23+
let testProjectDirectory = '';
24+
let execSpy: jest.SpyInstance;
25+
const realExecute = execModule.executeCommand;
26+
27+
beforeAll(() => {
28+
// Create a temporary directory for the test project
29+
testProjectDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'aws-luminarlz-cli-test-'));
30+
31+
// Change working directory for test
32+
process.chdir(testProjectDirectory);
33+
});
34+
35+
beforeEach(() => {
36+
// Clear and reset mocks before each test
37+
ssmMock.reset();
38+
stsMock.reset();
39+
organizationsMock.reset();
40+
ssoAdminMock.reset();
41+
jest.clearAllMocks();
42+
43+
// Set up executeCommand spy with passthrough. Intercept only cloning and building of the LZA repo
44+
// Do NOT intercept npx cdk synth
45+
execSpy = jest.spyOn(execModule, 'executeCommand').mockImplementation(((command: any, opts: any) => {
46+
if (typeof command === 'string') {
47+
if (command.startsWith('git clone ')) {
48+
return Promise.resolve({ stdout: '', stderr: '' } as any) as any;
49+
}
50+
if (command.includes('yarn')) {
51+
return Promise.resolve({ stdout: '', stderr: '' } as any) as any;
52+
}
53+
}
54+
return (realExecute as any)(command, opts);
55+
}) as any);
56+
57+
// Mock SSM parameter for AWS Accelerator version
58+
ssmMock.on(GetParameterCommand).resolves({
59+
Parameter: {
60+
Name: AWS_ACCELERATOR_INSTALLER_STACK_VERSION_SSM_PARAMETER_NAME,
61+
Value: '1.12.2',
62+
Type: 'String',
63+
},
64+
});
65+
66+
// Mock STS GetCallerIdentity
67+
stsMock.on(GetCallerIdentityCommand).resolves({
68+
Account: '123456789012',
69+
Arn: 'arn:aws:iam::123456789012:role/Admin',
70+
UserId: 'AROAEXAMPLE123',
71+
});
72+
73+
// Mock Organizations DescribeOrganization
74+
organizationsMock.on(DescribeOrganizationCommand).resolves({
75+
Organization: {
76+
Id: 'o-exampleorg',
77+
Arn: 'arn:aws:organizations::123456789012:organization/o-exampleorg',
78+
MasterAccountId: '123456789012',
79+
},
80+
});
81+
82+
// Mock Organizations ListRoots
83+
organizationsMock.on(ListRootsCommand).resolves({
84+
Roots: [
85+
{
86+
Id: 'r-exampleroot',
87+
Arn: 'arn:aws:organizations::123456789012:root/o-exampleorg/r-exampleroot',
88+
Name: 'Root',
89+
},
90+
],
91+
});
92+
93+
// Mock SSO Admin ListInstances
94+
ssoAdminMock.on(ListInstancesCommand).resolves({
95+
Instances: [
96+
{
97+
InstanceArn: 'arn:aws:sso:::instance/ssoins-example',
98+
IdentityStoreId: 'd-example123',
99+
},
100+
],
101+
});
102+
});
103+
104+
it('should synthesize and validate after initializing a project with the specified blueprint', async () => {
105+
// Run the init command to set up the project
106+
const initCli = new Cli();
107+
initCli.register(Init);
108+
const initExitCode = await initCli.run([
109+
'init',
110+
'--blueprint', 'foundational',
111+
'--accounts-root-email', 'test@example.com',
112+
'--region', 'us-east-1',
113+
'--force',
114+
]);
115+
116+
// Install dependencies after initialization
117+
await execModule.executeCommand('npm install', { cwd: testProjectDirectory });
118+
119+
// Verify init was successful
120+
expect(initExitCode).toBe(0);
121+
122+
// Now create CLI instance with LzaConfigValidate command
123+
const validateCli = new Cli();
124+
validateCli.register(LzaConfigValidate);
125+
126+
// Run the lza config validate command
127+
const validateExitCode = await validateCli.run(['lza', 'config', 'validate']);
128+
129+
// Verify command was successful
130+
expect(validateExitCode).toBe(0);
131+
132+
// Verify that the accelerator config output directory was created and contains files
133+
const config = loadConfigSync();
134+
const outPath = path.join(testProjectDirectory, config.awsAcceleratorConfigOutPath);
135+
expect(fs.existsSync(outPath)).toBe(true);
136+
const outFiles = fs.readdirSync(outPath, { recursive: false });
137+
expect(outFiles.length).toBeGreaterThan(0);
138+
139+
// Verify that cdk.out templates were copied into the output directory
140+
const cdkOutPath = path.join(outPath, config.cdkOutPath);
141+
expect(fs.existsSync(cdkOutPath)).toBe(true);
142+
const cdkFiles = fs
143+
.readdirSync(cdkOutPath, { recursive: true })
144+
.filter((f) => f.toString().endsWith('.template.json'));
145+
expect(cdkFiles.length).toBeGreaterThan(0);
146+
147+
// Ensure executeCommand was called to run validate-config with correct parameters
148+
const expectedConfigDir = path.join(testProjectDirectory, config.awsAcceleratorConfigOutPath);
149+
const expectedCwd = path.join(getCheckoutPath(), LZA_SOURCE_PATH);
150+
const validateCalls = execSpy.mock.calls.filter(([cmd]) => typeof cmd === 'string' && cmd.startsWith('yarn validate-config'));
151+
expect(validateCalls.length).toBe(1);
152+
expect(validateCalls[0][0]).toBe(`yarn validate-config ${expectedConfigDir}`);
153+
expect(validateCalls[0][1]?.cwd).toBe(expectedCwd);
154+
155+
// Ensure executeCommand was called to clone the repository and then build it
156+
const cloneCalls = execSpy.mock.calls.filter(([cmd]) => typeof cmd === 'string' && cmd.startsWith('git clone '));
157+
expect(cloneCalls.length).toBe(1);
158+
159+
const buildCalls = execSpy.mock.calls.filter(([cmd, opts]) => typeof cmd === 'string' && cmd.includes('yarn') && cmd.includes('build') && opts?.cwd === expectedCwd);
160+
expect(buildCalls.length).toBe(1);
161+
162+
// Verify that yarn && yarn build was called after git clone
163+
const cloneIndex = execSpy.mock.calls.findIndex(([cmd]) => typeof cmd === 'string' && cmd.startsWith('git clone '));
164+
const buildIndex = execSpy.mock.calls.findIndex(([cmd, opts]) => typeof cmd === 'string' && cmd.includes('yarn') && cmd.includes('build') && opts?.cwd === expectedCwd);
165+
expect(cloneIndex).toBeGreaterThanOrEqual(0);
166+
expect(buildIndex).toBeGreaterThan(cloneIndex);
167+
}, 120 * 1000);
168+
});

0 commit comments

Comments
 (0)