Skip to content

Commit

Permalink
feat: templategen command e2e integration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Sanay Yogesh Shah committed Oct 23, 2024
1 parent ec9a9f6 commit 8f2ede1
Show file tree
Hide file tree
Showing 13 changed files with 1,005 additions and 39 deletions.
1 change: 1 addition & 0 deletions packages/amplify-migration-e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"@aws-sdk/client-appsync": "^3.666.0",
"@aws-sdk/client-cloudcontrol": "^3.658.1",
"@aws-sdk/client-cognito-identity": "^3.670.0",
"@aws-sdk/client-s3": "^3.674.0",
"execa": "^5.1.1",
"fs-extra": "^8.1.0",
"lodash": "^4.17.21"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import path from 'node:path';
import assert from 'node:assert';
import { createNewProjectDir, npmInstall, deleteS3Bucket } from '@aws-amplify/amplify-e2e-core';
import { assertDefaultGen1Setup } from '../assertions';
import { setupAndPushDefaultGen1Project, runCodegenCommand, runGen2SandboxCommand, cleanupProjects } from '..';
import { copyFunctionFile } from '../function_utils';
import { copyGen1Schema } from '../api_utils';
import { updatePackageDependency } from '../updatePackageJson';
import { createS3Bucket } from '../sdk_calls';
import { runTemplategenCommand, stackRefactor } from '../templategen';

void describe('Templategen E2E tests', () => {
void describe('Full Migration Templategen Flow', () => {
let projRoot: string;
let projName: string;
let bucketName: string;

beforeEach(async () => {
const baseDir = process.env.INIT_CWD ?? process.cwd();
projRoot = await createNewProjectDir('templategen_e2e_flow_test', path.join(baseDir, '..', '..'));
projName = `test${Math.floor(Math.random() * 1000000)}`;
bucketName = `testbucket${Math.floor(Math.random() * 1000000)}`;
});

afterEach(async () => {
await cleanupProjects(projRoot);
await deleteS3Bucket(bucketName);
});

void it('should init a project & add auth, function, storage, api with defaults & perform full migration templategen flow', async () => {
await setupAndPushDefaultGen1Project(projRoot, projName);
const { gen1StackName, gen1FunctionName, gen1Region } = await assertDefaultGen1Setup(projRoot);
await createS3Bucket(bucketName, gen1Region);
assert(gen1StackName);
await runCodegenCommand(projRoot);
await copyFunctionFile(projRoot, 'function', gen1FunctionName);
await copyGen1Schema(projRoot, projName);

// TODO: replace below line with correct package version
await updatePackageDependency(projRoot, '@aws-amplify/backend');

await npmInstall(projRoot);
const gen2StackName = await runGen2SandboxCommand(projRoot);
assert(gen2StackName);
await runTemplategenCommand(projRoot, gen1StackName, gen2StackName);
// await stackRefactor(projRoot, 'auth', bucketName);
await stackRefactor(projRoot, 'storage', bucketName);
});
});
});
20 changes: 15 additions & 5 deletions packages/amplify-migration-e2e/src/assertions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ import { removeProperties } from '.';
import { $TSAny } from '@aws-amplify/amplify-cli-core';
import assert from 'node:assert';

async function assertUserPool(gen1Meta: $TSAny, gen1Region: string) {
export async function assertUserPool(gen1Meta: $TSAny, gen1Region: string) {
const { UserPoolId: gen1UserPoolId } = Object.keys(gen1Meta.auth).map((key) => gen1Meta.auth[key])[0].output;
const cloudUserPool = await getUserPool(gen1UserPoolId, gen1Region);
expect(cloudUserPool.UserPool).toBeDefined();
return { gen1UserPoolId };
}

async function assertUserPoolClients(gen1Meta: $TSAny, gen1Region: string) {
export async function assertUserPoolClients(gen1Meta: $TSAny, gen1Region: string) {
const {
UserPoolId: userPoolId,
AppClientIDWeb: appClientIdWeb,
Expand All @@ -32,7 +32,7 @@ async function assertUserPoolClients(gen1Meta: $TSAny, gen1Region: string) {
return { gen1ClientIds };
}

async function assertIdentityPool(gen1Meta: $TSAny, gen1Region: string) {
export async function assertIdentityPool(gen1Meta: $TSAny, gen1Region: string) {
const { IdentityPoolId: gen1IdentityPoolId } = Object.keys(gen1Meta.auth).map((key) => gen1Meta.auth[key])[0].output;
const cloudIdentityPool = await getIdentityPool(gen1IdentityPoolId, gen1Region);
expect(cloudIdentityPool).toBeDefined();
Expand All @@ -48,7 +48,7 @@ async function assertFunction(gen1Meta: $TSAny, gen1Region: string) {
return { gen1FunctionName };
}

async function assertStorage(gen1Meta: $TSAny, gen1Region: string) {
export async function assertStorage(gen1Meta: $TSAny, gen1Region: string) {
const { BucketName: gen1BucketName } = Object.keys(gen1Meta.storage).map((key) => gen1Meta.storage[key])[0].output;
expect(gen1BucketName).toBeDefined();
const bucketExists = await checkIfBucketExists(gen1BucketName, gen1Region);
Expand Down Expand Up @@ -88,6 +88,7 @@ async function assertUserPoolGroups(gen1Meta: $TSAny) {

export async function assertDefaultGen1Setup(projRoot: string) {
const gen1Meta = getProjectMeta(projRoot);
const gen1StackName = gen1Meta.providers.awscloudformation.StackName;
const gen1Region = gen1Meta.providers.awscloudformation.Region;
const { gen1UserPoolId } = await assertUserPool(gen1Meta, gen1Region);
const { gen1FunctionName } = await assertFunction(gen1Meta, gen1Region);
Expand All @@ -96,7 +97,16 @@ export async function assertDefaultGen1Setup(projRoot: string) {
const { gen1GraphqlApiId } = await assertAPI(gen1Meta, gen1Region);
const { gen1IdentityPoolId } = await assertIdentityPool(gen1Meta, gen1Region);
const { gen1ClientIds } = await assertUserPoolClients(gen1Meta, gen1Region);
return { gen1UserPoolId, gen1ClientIds, gen1IdentityPoolId, gen1FunctionName, gen1BucketName, gen1GraphqlApiId, gen1Region };
return {
gen1StackName,
gen1UserPoolId,
gen1ClientIds,
gen1IdentityPoolId,
gen1FunctionName,
gen1BucketName,
gen1GraphqlApiId,
gen1Region,
};
}

export async function assertAuthWithMaxOptionsGen1Setup(projRoot: string) {
Expand Down
9 changes: 9 additions & 0 deletions packages/amplify-migration-e2e/src/envVariables.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
type EnvVariableAction = 'SET' | 'DELETE';

export function toggleEnvVariable(name: string, option: EnvVariableAction, value?: string) {
if (option === 'SET') {
process.env[name] = value;
} else if (option === 'DELETE') {
delete process.env[name];
}
}
42 changes: 42 additions & 0 deletions packages/amplify-migration-e2e/src/gen1ResourceDetailsFetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import path from 'path';
import { RefactorCategory } from './templategen';
import { getProjectMeta } from '@aws-amplify/amplify-e2e-core';
import { assertIdentityPool, assertStorage, assertUserPool, assertUserPoolClients } from './assertions';
import { getResourceDetails } from './sdk_calls';

async function getGen1AuthResourceDetails(projRoot: string) {
const gen1ProjRoot = path.join(projRoot, '.amplify', 'migration');
const gen1Meta = getProjectMeta(gen1ProjRoot);
const gen1Region = gen1Meta.providers.awscloudformation.Region;
const { gen1UserPoolId } = await assertUserPool(gen1Meta, gen1Region);
const { gen1IdentityPoolId } = await assertIdentityPool(gen1Meta, gen1Region);
const { gen1ClientIds } = await assertUserPoolClients(gen1Meta, gen1Region);
const [gen1ClientIdWeb, gen1ClientId] = gen1ClientIds;

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused variable gen1ClientId.
const gen1ResourceIds = [gen1UserPoolId, gen1IdentityPoolId, gen1ClientIdWeb];

const gen1ResourceDetails = await Promise.all([
getResourceDetails('AWS::Cognito::UserPool', gen1UserPoolId, gen1Region),
getResourceDetails('AWS::Cognito::IdentityPool', gen1IdentityPoolId, gen1Region),
getResourceDetails('AWS::Cognito::UserPoolClient', `${gen1UserPoolId}|${gen1ClientIdWeb}`, gen1Region),
]);

return { gen1ResourceIds, gen1ResourceDetails };
}

async function getGen1StorageResourceDetails(projRoot: string) {
const gen1ProjRoot = path.join(projRoot, '.amplify', 'migration');
const gen1Meta = getProjectMeta(gen1ProjRoot);
const gen1Region = gen1Meta.providers.awscloudformation.Region;
const { gen1BucketName } = await assertStorage(gen1Meta, gen1Region);
const gen1ResourceIds = [gen1BucketName];
const gen1ResourceDetails = await getResourceDetails('AWS::S3::Bucket', gen1BucketName, gen1Region);
return { gen1ResourceIds, gen1ResourceDetails };
}

export async function getGen1ResourceDetails(projRoot: string, category: RefactorCategory) {
if (category === 'auth') {
return await getGen1AuthResourceDetails(projRoot);
} else {
return await getGen1StorageResourceDetails(projRoot);
}
}
37 changes: 37 additions & 0 deletions packages/amplify-migration-e2e/src/gen2ResourceDetailsFetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { getProjectOutputs } from './projectOutputs';
import { getResourceDetails } from './sdk_calls';
import { RefactorCategory } from './templategen';

async function getGen2AuthResourceDetails(projRoot: string) {
const gen2Meta = getProjectOutputs(projRoot);
const gen2Region = gen2Meta.auth.aws_region;
const gen2UserPoolId = gen2Meta.auth.user_pool_id;
const gen2ClientIdWeb = gen2Meta.auth.user_pool_client_id;
const gen2IdentityPoolId = gen2Meta.auth.identity_pool_id;
const gen2ResourceIds = [gen2UserPoolId, gen2IdentityPoolId, gen2ClientIdWeb];

const gen2ResourceDetails = await Promise.all([
getResourceDetails('AWS::Cognito::UserPool', gen2UserPoolId, gen2Region),
getResourceDetails('AWS::Cognito::IdentityPool', gen2IdentityPoolId, gen2Region),
getResourceDetails('AWS::Cognito::UserPoolClient', `${gen2UserPoolId}|${gen2ClientIdWeb}`, gen2Region),
]);

return { gen2ResourceIds, gen2ResourceDetails };
}

async function getGen2StorageResourceDetails(projRoot: string) {
const gen2Meta = getProjectOutputs(projRoot);
const gen2Region = gen2Meta.auth.aws_region;
const gen2BucketName = gen2Meta.storage.bucket_name;
const gen2ResourceIds = [gen2BucketName];
const gen2ResourceDetails = await getResourceDetails('AWS::S3::Bucket', gen2BucketName, gen2Region);
return { gen2ResourceIds, gen2ResourceDetails };
}

export async function getGen2ResourceDetails(projRoot: string, category: RefactorCategory) {
if (category === 'auth') {
return await getGen2AuthResourceDetails(projRoot);
} else {
return await getGen2StorageResourceDetails(projRoot);
}
}
36 changes: 3 additions & 33 deletions packages/amplify-migration-e2e/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
addAuthWithDefault,
amplifyPush,
getNpxPath,
nspawn as spawn,
addS3WithGuestAccess,
addFunction,
functionBuild,
Expand All @@ -22,13 +21,15 @@ import {
import path from 'node:path';
import { unset } from 'lodash';
import execa from 'execa';
import { deleteGen2Sandbox } from './sandbox';

export * from './sdk_calls';
export * from './assertions';
export * from './projectOutputs';
export * from './updatePackageJson';
export * from './sandbox';

const pushTimeoutMS = 1000 * 60 * 20; // 20 minutes;
export const pushTimeoutMS = 1000 * 60 * 20; // 20 minutes;

export async function setupAndPushDefaultGen1Project(projRoot: string, projName: string) {
await initJSProjectWithProfile(projRoot, { name: projName, disableAmplifyAppCreation: false, includeGen2RecommendationPrompt: false });
Expand Down Expand Up @@ -75,37 +76,6 @@ export function runCodegenCommand(cwd: string) {
}
}

export async function runGen2SandboxCommand(cwd: string) {
const processResult = execa.sync(getNpxPath(), ['ampx', 'sandbox', '--once'], {
cwd,
env: { ...process.env, npm_config_user_agent: 'npm' },
encoding: 'utf-8',
});
if (processResult.exitCode === 0) {
const match = processResult.stdout.match(/arn:aws:cloudformation:.*:stack\/([^/]+)\//);
if (match) {
return match[1];
} else {
throw new Error('Stack name not found in the command output');
}
} else {
throw new Error(`Sandbox command exit code: ${processResult.exitCode}, message: ${processResult.stderr}`);
}
}

function deleteGen2Sandbox(cwd: string) {
return spawn(getNpxPath(), ['ampx', 'sandbox', 'delete'], {
cwd,
stripColors: true,
noOutputTimeout: pushTimeoutMS,
env: { ...process.env, npm_config_user_agent: 'npm' },
})
.wait("Are you sure you want to delete all the resources in your sandbox environment (This can't be undone)?")
.sendConfirmYes()
.wait('Finished deleting.')
.runAsync();
}

export async function cleanupProjects(cwd: string) {
await deleteGen1Project(path.join(cwd, '.amplify', 'migration'));
await deleteGen2Sandbox(cwd);
Expand Down
35 changes: 35 additions & 0 deletions packages/amplify-migration-e2e/src/migrationReadmeParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
function extractContent(readmeContent: string, startRegex: string, endRegex: string) {
const pattern = new RegExp(`${startRegex}([\\s\\S]*?)${endRegex}`, 'i');
const match = readmeContent.match(pattern);

if (match && match[1]) {
return match[1].trim();
}
throw new Error('README file parsing failed to get the stack refactor commands');
}

function extractCommands(readmeContent: string) {
const pattern = /```([\s\S]*?)```/g;
const matches = readmeContent.matchAll(pattern);
const commands = [];

for (const match of matches) {
if (match[1]) {
commands.push(match[1].trim());
}
}
if (commands.length === 0) {
throw new Error('README file parsing failed to get the stack refactor commands');
}
return commands;
}

export function getCommandsFromReadme(readmeContent: string) {
const step1Content = extractContent(readmeContent, '### STEP 1', '#### Rollback step');
const step2Content = extractContent(readmeContent, '### STEP 2', '#### Rollback step');
const step3Content = extractContent(readmeContent, '### STEP 3', '#### Rollback step');
const step1Commands = extractCommands(step1Content);
const step2commands = extractCommands(step2Content);
const step3Commands = extractCommands(step3Content);
return { step1Commands, step2commands, step3Commands };
}
34 changes: 34 additions & 0 deletions packages/amplify-migration-e2e/src/sandbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { getNpxPath, nspawn as spawn } from '@aws-amplify/amplify-e2e-core';
import { pushTimeoutMS } from '.';
import execa from 'execa';

export async function runGen2SandboxCommand(cwd: string) {
const processResult = execa.sync(getNpxPath(), ['ampx', 'sandbox', '--once'], {
cwd,
env: { ...process.env, npm_config_user_agent: 'npm' },
encoding: 'utf-8',
});
if (processResult.exitCode === 0) {
const match = processResult.stdout.match(/arn:aws:cloudformation:.*:stack\/([^/]+)\//);
if (match) {
return match[1];
} else {
throw new Error('Stack name not found in the command output');
}
} else {
throw new Error(`Sandbox command exit code: ${processResult.exitCode}, message: ${processResult.stderr}`);
}
}

export function deleteGen2Sandbox(cwd: string) {
return spawn(getNpxPath(), ['ampx', 'sandbox', 'delete'], {
cwd,
stripColors: true,
noOutputTimeout: pushTimeoutMS,
env: { ...process.env, npm_config_user_agent: 'npm' },
})
.wait("Are you sure you want to delete all the resources in your sandbox environment (This can't be undone)?")
.sendConfirmYes()
.wait('Finished deleting.')
.runAsync();
}
12 changes: 12 additions & 0 deletions packages/amplify-migration-e2e/src/sdk_calls.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
import { CloudControlClient, GetResourceCommand } from '@aws-sdk/client-cloudcontrol';
import { AppSyncClient, GetDataSourceCommand } from '@aws-sdk/client-appsync';
import { CognitoIdentityClient, DescribeIdentityPoolCommand } from '@aws-sdk/client-cognito-identity';
import { S3Client, CreateBucketCommand, BucketLocationConstraint } from '@aws-sdk/client-s3';

export async function createS3Bucket(bucketName: string, region: string) {
const client = new S3Client({ region });
const command = new CreateBucketCommand({
Bucket: bucketName,
CreateBucketConfiguration: {
LocationConstraint: region as BucketLocationConstraint,
},
});
const response = await client.send(command);
return response;
}
export async function getAppSyncDataSource(apiId: string, dataSourceName: string, region: string) {
const client = new AppSyncClient({ region });
const command = new GetDataSourceCommand({
Expand Down
Loading

0 comments on commit 8f2ede1

Please sign in to comment.