Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .projen/deps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions .projen/tasks.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions .projenrc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ const project = new typescript.TypeScriptProject({
'typescript',
'zip-lib',
],
devDeps: [
'aws-sdk-client-mock',
],
sampleCode: false,
gitignore: ['/blueprints/**/package-lock.json', '/blueprints/**/yarn.lock'],
githubOptions: {
Expand Down
1 change: 1 addition & 0 deletions package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

161 changes: 161 additions & 0 deletions test/commands/deploy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import fs from 'fs';
import os from 'os';
import path from 'path';
import { DescribeOrganizationCommand, ListRootsCommand, OrganizationsClient } from '@aws-sdk/client-organizations';
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { GetParameterCommand, SSMClient } from '@aws-sdk/client-ssm';
import { ListInstancesCommand, SSOAdminClient } from '@aws-sdk/client-sso-admin';
import { GetCallerIdentityCommand, STSClient } from '@aws-sdk/client-sts';
import { mockClient } from 'aws-sdk-client-mock';
import { Cli } from 'clipanion';
import { Deploy } from '../../src/commands/deploy';
import { Init } from '../../src/commands/init';
import {
AWS_ACCELERATOR_INSTALLER_STACK_VERSION_SSM_PARAMETER_NAME,
awsAcceleratorConfigBucketName, loadConfigSync,
} from '../../src/config';
import * as assets from '../../src/core/customizations/assets';
import { executeCommand } from '../../src/core/util/exec';

// Mock the assets module
jest.mock('../../src/core/customizations/assets', () => ({
customizationsPublishCdkAssets: jest.fn(),
}));

describe('Deploy command', () => {
// Create mocks for AWS services
const ssmMock = mockClient(SSMClient);
const stsMock = mockClient(STSClient);
const organizationsMock = mockClient(OrganizationsClient);
const ssoAdminMock = mockClient(SSOAdminClient);
const s3Mock = mockClient(S3Client);

let testProjectDirectory = '';

// Create spies for the functions
let assetsSpy: jest.SpyInstance;

beforeAll(() => {
// Create a temporary directory for the test project
testProjectDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'aws-luminarlz-cli-test-'));

// Change working directory for test
process.chdir(testProjectDirectory);
});

beforeEach(() => {
// Clear mocks before each test
ssmMock.reset();
stsMock.reset();
organizationsMock.reset();
ssoAdminMock.reset();
s3Mock.reset();

// Reset all mocks
jest.clearAllMocks();

// Set up spies for the functions
assetsSpy = jest.spyOn(assets, 'customizationsPublishCdkAssets').mockResolvedValue();

// Mock SSM parameter for AWS Accelerator version
ssmMock.on(GetParameterCommand).resolves({
Parameter: {
Name: AWS_ACCELERATOR_INSTALLER_STACK_VERSION_SSM_PARAMETER_NAME,
Value: '1.12.2',
Type: 'String',
},
});

// Mock STS GetCallerIdentity
stsMock.on(GetCallerIdentityCommand).resolves({
Account: '123456789012',
Arn: 'arn:aws:iam::123456789012:role/Admin',
UserId: 'AROAEXAMPLE123',
});

// Mock Organizations DescribeOrganization
organizationsMock.on(DescribeOrganizationCommand).resolves({
Organization: {
Id: 'o-exampleorg',
Arn: 'arn:aws:organizations::123456789012:organization/o-exampleorg',
MasterAccountId: '123456789012',
},
});

// Mock Organizations ListRoots
organizationsMock.on(ListRootsCommand).resolves({
Roots: [
{
Id: 'r-exampleroot',
Arn: 'arn:aws:organizations::123456789012:root/o-exampleorg/r-exampleroot',
Name: 'Root',
},
],
});

// Mock SSO Admin ListInstances
ssoAdminMock.on(ListInstancesCommand).resolves({
Instances: [
{
InstanceArn: 'arn:aws:sso:::instance/ssoins-example',
IdentityStoreId: 'd-example123',
},
],
});
});

it('should deploy after initializing a project with the specified blueprint', async () => {
// Run the init command to set up the project
const initCli = new Cli();
initCli.register(Init);
const initExitCode = await initCli.run([
'init',
'--blueprint', 'foundational',
'--accounts-root-email', 'test@example.com',
'--region', 'us-east-1',
'--force',
]);

// Install dependencies after initialization
await executeCommand('npm install', { cwd: testProjectDirectory });

// Verify init was successful
expect(initExitCode).toBe(0);

// Now create CLI instance with Deploy command
const deployCli = new Cli();
deployCli.register(Deploy);

// Run the deploy command
const deployExitCode = await deployCli.run(['deploy']);

// Verify deploy was successful
expect(deployExitCode).toBe(0);

// Verify that the accelerator config output directory was created and contains files
const config = loadConfigSync();
const outPath = path.join(testProjectDirectory, config.awsAcceleratorConfigOutPath);
expect(fs.existsSync(outPath)).toBe(true);
const outFiles = fs.readdirSync(outPath, { recursive: false });
expect(outFiles.length).toBeGreaterThan(0);

// Verify that cdk.out templates were copied into the output directory
const cdkOutPath = path.join(outPath, config.cdkOutPath);
expect(fs.existsSync(cdkOutPath)).toBe(true);
const cdkFiles = fs
.readdirSync(cdkOutPath, { recursive: true })
.filter((f) => f.toString().endsWith('.template.json'));
expect(cdkFiles.length).toBeGreaterThan(0);

// Verify that accelerator config was uploaded to S3
const s3Calls = s3Mock.commandCalls(PutObjectCommand);
const callInput = s3Calls[0].args[0].input;
expect(callInput.Bucket).toBe(awsAcceleratorConfigBucketName(config));
expect(callInput.Key).toBe(config.awsAcceleratorConfigDeploymentArtifactPath);
expect(Buffer.isBuffer(callInput.Body)).toBe(true);


// Verify that the customizationsPublishCdkAssets function was called
expect(assetsSpy).toHaveBeenCalled();
}, 120 * 1000);
});
125 changes: 125 additions & 0 deletions test/commands/init.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import * as fs from 'fs';
import os from 'node:os';
import * as path from 'path';
import { OrganizationsClient, DescribeOrganizationCommand, ListRootsCommand } from '@aws-sdk/client-organizations';
import { SSMClient, GetParameterCommand } from '@aws-sdk/client-ssm';
import { SSOAdminClient, ListInstancesCommand } from '@aws-sdk/client-sso-admin';
import { STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts';
import { mockClient } from 'aws-sdk-client-mock';
import { Cli } from 'clipanion';
import { Init } from '../../src/commands/init';
import {
AWS_ACCELERATOR_INSTALLER_STACK_VERSION_SSM_PARAMETER_NAME,
} from '../../src/config';

describe('Init Command', () => {
// Mock AWS clients
const stsMock = mockClient(STSClient);
const orgMock = mockClient(OrganizationsClient);
const ssoMock = mockClient(SSOAdminClient);
const ssmMock = mockClient(SSMClient);

const originalCwd = process.cwd();
let testProjectDirectory = '';

beforeAll(() => {
// Create a temporary directory for the test project
testProjectDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'aws-luminarlz-cli-test-'));

// Change working directory for test
process.chdir(testProjectDirectory);
});

afterAll(() => {
// Restore original working directory
process.chdir(originalCwd);
});

beforeEach(() => {
// Reset mocks
jest.clearAllMocks();
stsMock.reset();
orgMock.reset();
ssoMock.reset();
ssmMock.reset();

// Setup AWS mock responses
stsMock.on(GetCallerIdentityCommand).resolves({
Account: '123456789012',
Arn: 'arn:aws:iam::123456789012:user/test-user',
UserId: 'AIDATEST123456',
});

orgMock.on(DescribeOrganizationCommand).resolves({
Organization: {
Id: 'o-abcdef1234',
Arn: 'arn:aws:organizations::123456789012:organization/o-abcdef1234',
FeatureSet: 'ALL',
MasterAccountArn: 'arn:aws:organizations::123456789012:account/o-abcdef1234/123456789012',
MasterAccountEmail: 'master@example.com',
MasterAccountId: '123456789012',
},
});

orgMock.on(ListRootsCommand).resolves({
Roots: [
{
Id: 'r-abcd1234',
Arn: 'arn:aws:organizations::123456789012:root/o-abcdef1234/r-abcd1234',
Name: 'Root',
PolicyTypes: [],
},
],
});

ssoMock.on(ListInstancesCommand).resolves({
Instances: [
{
InstanceArn: 'arn:aws:sso:::instance/ssoins-12345678901234567',
IdentityStoreId: 'd-12345678ab',
},
],
});

ssmMock.on(GetParameterCommand).resolves({
Parameter: {
Name: AWS_ACCELERATOR_INSTALLER_STACK_VERSION_SSM_PARAMETER_NAME,
Value: '1.12.2',
Type: 'String',
},
});
});

it('should initialize a project with the specified blueprint and create a config.ts with expected content', async () => {
// Create CLI instance with Init command
const cli = new Cli();
cli.register(Init);

// Define test params
const region = 'us-east-1';
const email = 'test@example.com';

// Run the command
const exitCode = await cli.run([
'init',
'--region', region,
'--accounts-root-email', email,
]);

// Verify successful execution
expect(exitCode).toBe(0);

const configPath = path.join(testProjectDirectory, 'config.ts');
expect(fs.existsSync(configPath)).toBe(true);

const configContent = fs.readFileSync(configPath, 'utf8');

// Assert constants rendered into config.ts
expect(configContent).toContain("export const AWS_ACCELERATOR_VERSION = '1.12.2'");
expect(configContent).toContain("export const MANAGEMENT_ACCOUNT_ID = '123456789012'");
expect(configContent).toContain("export const ORGANIZATION_ID = 'o-abcdef1234'");
expect(configContent).toContain("export const ROOT_OU_ID = 'r-abcd1234'");
expect(configContent).toContain("export const AWS_ACCOUNTS_ROOT_EMAIL = 'test@example.com'");
expect(configContent).toContain("export const HOME_REGION = 'us-east-1'");
});
});
Loading