Skip to content

Commit

Permalink
refactor: organise file structure and modify logic to get gen2StackName
Browse files Browse the repository at this point in the history
  • Loading branch information
Sanay Yogesh Shah committed Oct 10, 2024
1 parent f8bf860 commit d310c32
Show file tree
Hide file tree
Showing 12 changed files with 845 additions and 475 deletions.
1 change: 1 addition & 0 deletions .eslint-dictionary.json
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@
"multifactor",
"multipart",
"mutex",
"mygen2app",
"namespace",
"netcoreapp",
"netmask",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,51 @@
import path from 'node:path';
import assert from 'node:assert';
import { createNewProjectDir } from '@aws-amplify/amplify-e2e-core';
import {
cleanupProjects,
setupAndPushGen1Project,
assertGen1Setup,
runCodegenCommand,
runGen2SandboxCommand,
assertUserPoolResource,
assertStorageResource,
assertFunctionResource,
assertDataResource,
copyFunctionFile,
copyGen1Schema,
} from '../helpers';
import { createGen2Renderer } from '@aws-amplify/amplify-gen2-codegen';
import { copyFunctionFile } from '../function-utils';
import { copyGen1Schema } from '../api-utils';
import { cleanupProjects, setupAndPushGen1Project, runCodegenCommand, runGen2SandboxCommand } from '..';
import { assertGen1Setup, assertUserPoolResource, assertStorageResource, assertFunctionResource, assertDataResource } from '../assertions';

void describe('Migration Codegen E2E tests', () => {
let projRoot: string;
beforeEach(async () => {
const baseDir = process.env.INIT_CWD ?? process.cwd();
projRoot = await createNewProjectDir('codegen_e2e_flow_test', path.join(baseDir, '..', '..'));
void describe('Codegen E2E tests', () => {
void describe('render pipeline', () => {
void it('renders a project with no parameters', async () => {
const pipeline = createGen2Renderer({
outputDir: path.join(process.env.INIT_CWD ?? './', 'output'),
auth: {
loginOptions: {
email: true,
},
},
});
await assert.doesNotReject(pipeline.render);
});
});
void describe('Full Migration Codegen Flow', () => {
let projRoot: string;
let projName: string;

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

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

void it('performs full migration codegen flow with backend', async () => {
await setupAndPushGen1Project(projRoot, 'CodegenTest');
const { gen1UserPoolId, gen1FunctionName, gen1BucketName, gen1GraphQLAPIId, gen1Region } = await assertGen1Setup(projRoot);
await assert.doesNotReject(runCodegenCommand(projRoot), 'Codegen failed');
await copyFunctionFile(projRoot, gen1FunctionName);
await copyGen1Schema(projRoot);
await assert.doesNotReject(runGen2SandboxCommand(projRoot), 'Gen2 CDK deployment failed');
await assertUserPoolResource(projRoot, gen1UserPoolId, gen1Region);
await assertStorageResource(projRoot, gen1BucketName, gen1Region);
await assertFunctionResource(projRoot, gen1FunctionName, gen1Region);
await assertDataResource(projRoot, gen1GraphQLAPIId, gen1Region);
void it('should init a project & add auth, function, storage, api with defaults & perform full migration codegen flow', async () => {
await setupAndPushGen1Project(projRoot, projName);
const { gen1UserPoolId, gen1FunctionName, gen1BucketName, gen1GraphqlApiId, gen1Region } = await assertGen1Setup(projRoot);
await assert.doesNotReject(runCodegenCommand(projRoot), 'Codegen failed');
await copyFunctionFile(projRoot, gen1FunctionName);
await copyGen1Schema(projRoot, projName);
const gen2StackName = await runGen2SandboxCommand(projRoot);
await assertUserPoolResource(projRoot, gen1UserPoolId, gen1Region);
await assertStorageResource(projRoot, gen1BucketName, gen1Region);
await assertFunctionResource(projRoot, gen2StackName, gen1FunctionName, gen1Region);
await assertDataResource(projRoot, gen2StackName, gen1GraphqlApiId, gen1Region);
});
});
});

This file was deleted.

29 changes: 29 additions & 0 deletions packages/amplify-migration-codegen-e2e/src/api-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { getProjectSchema } from '@aws-amplify/amplify-e2e-core';
import * as fs from 'fs-extra';
import path from 'node:path';

export function copyGen1Schema(projRoot: string, projName: string) {
const gen1Schema = getProjectSchema(path.join(projRoot, '.amplify', 'migration'), projName);

const dataResourcePath = path.join(projRoot, 'amplify', 'data', 'resource.ts');
const dataResourceContent = fs.readFileSync(dataResourcePath, 'utf-8');

const backendPath = path.join(projRoot, 'amplify', 'backend.ts');
let backendContent = fs.readFileSync(backendPath, 'utf-8');

const schemaRegex = /"TODO: Add your existing graphql schema here"/;
const updatedContent = dataResourceContent.replace(schemaRegex, `\`${gen1Schema.trim()}\``);

const errorRegex = /throw new Error\("TODO: Add Gen 1 GraphQL schema"\);?\s*/;
const finalContent = updatedContent.replace(errorRegex, '');

fs.writeFileSync(dataResourcePath, finalContent, 'utf-8');

const linesToAdd = `
const todoTable = backend.data.resources.cfnResources.additionalCfnResources['Todo'];
todoTable.addOverride('Properties.sseSpecification', { sseEnabled: false });
`;

backendContent += linesToAdd;
fs.writeFileSync(backendPath, backendContent, 'utf-8');
}
147 changes: 147 additions & 0 deletions packages/amplify-migration-codegen-e2e/src/assertions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import {
getProjectMeta,
getUserPool,
checkIfBucketExists,
getFunction,
getAppSyncApi,
describeCloudFormationStack,
} from '@aws-amplify/amplify-e2e-core';
import { getProjectOutputs } from './projectOutputs';
import { getAppSyncDataSource, getResourceDetails } from './sdk-calls';
import { removeProperties } from '.';

export async function assertGen1Setup(projRoot: string) {
const gen1Meta = getProjectMeta(projRoot);
const gen1Region = gen1Meta.providers.awscloudformation.Region;
const { UserPoolId: gen1UserPoolId } = Object.keys(gen1Meta.auth).map((key) => gen1Meta.auth[key])[0].output;
const { Arn: gen1FunctionArn, Name: gen1FunctionName } = Object.keys(gen1Meta.function).map((key) => gen1Meta.function[key])[0].output;
const { BucketName: gen1BucketName } = Object.keys(gen1Meta.storage).map((key) => gen1Meta.storage[key])[0].output;
const {
GraphQLAPIIdOutput: gen1GraphqlApiId,
GraphQLAPIEndpointOutput,
GraphQLAPIKeyOutput,
} = Object.keys(gen1Meta.api).map((key) => gen1Meta.api[key])[0].output;
const { graphqlApi } = await getAppSyncApi(gen1GraphqlApiId, gen1Region);

expect(gen1Region).toBeDefined();

const cloudUserPool = await getUserPool(gen1UserPoolId, gen1Region);
expect(cloudUserPool.UserPool).toBeDefined();

expect(gen1FunctionArn).toBeDefined();
expect(gen1FunctionName).toBeDefined();
const cloudFunction = await getFunction(gen1FunctionName, gen1Region);
expect(cloudFunction.Configuration?.FunctionArn).toEqual(gen1FunctionArn);

expect(gen1BucketName).toBeDefined();
const bucketExists = await checkIfBucketExists(gen1BucketName, gen1Region);
expect(bucketExists).toMatchObject({});

expect(gen1GraphqlApiId).toBeDefined();
expect(GraphQLAPIEndpointOutput).toBeDefined();
expect(GraphQLAPIKeyOutput).toBeDefined();

expect(graphqlApi).toBeDefined();
expect(graphqlApi?.apiId).toEqual(gen1GraphqlApiId);
return { gen1UserPoolId, gen1FunctionName, gen1BucketName, gen1GraphqlApiId, gen1Region };
}

export async function assertUserPoolResource(projRoot: string, gen1UserPoolId: string, gen1Region: string) {
const gen1Resource = await getResourceDetails('AWS::Cognito::UserPool', gen1UserPoolId, gen1Region);
removeProperties(gen1Resource, ['ProviderURL', 'ProviderName', 'UserPoolId', 'Arn']);
// TODO: remove below line after EmailMessage, EmailSubject, SmsMessage, SmsVerificationMessage, EmailVerificationMessage, EmailVerificationSubject, AccountRecoverySetting inconsistency is fixed
removeProperties(gen1Resource, [
'UserPoolTags',
'VerificationMessageTemplate.EmailMessage',
'VerificationMessageTemplate.EmailSubject',
'EmailVerificationSubject',
'AccountRecoverySetting',
'EmailVerificationMessage',
]);
const gen2Meta = getProjectOutputs(projRoot);
const gen2UserPoolId = gen2Meta.auth.user_pool_id;
const gen2Region = gen2Meta.auth.aws_region;
const gen2Resource = await getResourceDetails('AWS::Cognito::UserPool', gen2UserPoolId, gen2Region);
removeProperties(gen2Resource, ['ProviderURL', 'ProviderName', 'UserPoolId', 'Arn']);
// TODO: remove below line after EmailMessage, EmailSubject, SmsMessage, SmsVerificationMessage, EmailVerificationMessage, EmailVerificationSubject, AccountRecoverySetting inconsistency is fixed
removeProperties(gen2Resource, [
'UserPoolTags',
'VerificationMessageTemplate.EmailMessage',
'VerificationMessageTemplate.SmsMessage',
'VerificationMessageTemplate.EmailSubject',
'SmsVerificationMessage',
'EmailVerificationSubject',
'AccountRecoverySetting',
'EmailVerificationMessage',
]);
expect(gen2Resource).toEqual(gen1Resource);
}

export async function assertStorageResource(projRoot: string, gen1BucketName: string, gen1Region: string) {
const gen1Resource = await getResourceDetails('AWS::S3::Bucket', gen1BucketName, gen1Region);
removeProperties(gen1Resource, ['DualStackDomainName', 'DomainName', 'BucketName', 'Arn', 'RegionalDomainName', 'Tags', 'WebsiteURL']);
// TODO: remove below line after CorsConfiguration.CorsRules[0].Id inconsistency is fixed
removeProperties(gen1Resource, ['CorsConfiguration.CorsRules[0].Id']);

const gen2Meta = getProjectOutputs(projRoot);
const gen2BucketName = gen2Meta.storage.bucket_name;
const gen2Region = gen2Meta.storage.aws_region;
const gen2Resource = await getResourceDetails('AWS::S3::Bucket', gen2BucketName, gen2Region);
removeProperties(gen2Resource, ['DualStackDomainName', 'DomainName', 'BucketName', 'Arn', 'RegionalDomainName', 'Tags', 'WebsiteURL']);

expect(gen2Resource).toEqual(gen1Resource);
}

export async function assertFunctionResource(
projRoot: string,
gen2StackName: string | unknown,
gen1FunctionName: string,
gen1Region: string,
) {
const gen1Resource = await getResourceDetails('AWS::Lambda::Function', gen1FunctionName, gen1Region);
removeProperties(gen1Resource, ['Arn', 'FunctionName', 'LoggingConfig.LogGroup', 'Role']);
// TODO: remove below line after Tags inconsistency is fixed
removeProperties(gen1Resource, ['Tags']);

const gen2Meta = getProjectOutputs(projRoot);
const gen2Region = gen2Meta.auth.aws_region;
const outputs = (await describeCloudFormationStack(gen2StackName as string, gen2Region)).Outputs;
const gen2FunctionName = JSON.parse(outputs?.find((output) => output.OutputKey === 'definedFunctions')?.OutputValue ?? '[]')[0];
const gen2Resource = await getResourceDetails('AWS::Lambda::Function', gen2FunctionName, gen2Region);
removeProperties(gen2Resource, ['Arn', 'FunctionName', 'LoggingConfig.LogGroup', 'Role']);
// TODO: remove below line after Environment.Variables.AMPLIFY_SSM_ENV_CONFIG, Tags inconsistency is fixed
removeProperties(gen2Resource, ['Environment.Variables.AMPLIFY_SSM_ENV_CONFIG', 'Tags']);

expect(gen2Resource).toEqual(gen1Resource);
}

export async function assertDataResource(projRoot: string, gen2StackName: string | unknown, gen1GraphqlApiId: string, gen1Region: string) {
const gen1Resource = await getAppSyncApi(gen1GraphqlApiId, gen1Region);
const gen1DataSource = (await getAppSyncDataSource(gen1GraphqlApiId, 'TodoTable', gen1Region)) as Record<string, unknown>;
removeProperties(gen1DataSource, ['dataSourceArn', 'serviceRoleArn']);
removeProperties(gen1Resource.graphqlApi as Record<string, unknown>, ['name', 'apiId', 'arn', 'uris', 'tags', 'dns']);
// TODO: remove below line after authenticationType inconsistency is fixed
removeProperties(gen1Resource.graphqlApi as Record<string, unknown>, ['authenticationType']);

const gen2Meta = getProjectOutputs(projRoot);
const gen2Region = gen2Meta.data.aws_region;
const outputs = (await describeCloudFormationStack(gen2StackName as string, gen2Region)).Outputs;
const gen2GraphqlApiId = outputs?.find((output) => output.OutputKey === 'awsAppsyncApiId')?.OutputValue ?? '';
const gen2Resource = await getAppSyncApi(gen2GraphqlApiId, gen2Region);
const gen2DataSource = (await getAppSyncDataSource(gen2GraphqlApiId, 'TodoTable', gen1Region)) as Record<string, unknown>;
removeProperties(gen2DataSource, ['dataSourceArn', 'serviceRoleArn']);
removeProperties(gen2Resource.graphqlApi as Record<string, unknown>, [
'name',
'apiId',
'arn',
'uris',
'tags',
'additionalAuthenticationProviders',
'dns',
]);
// TODO: remove below line after authenticationType, userPoolConfig inconsistency is fixed
removeProperties(gen2Resource.graphqlApi as Record<string, undefined>, ['authenticationType', 'userPoolConfig']);

expect(gen2DataSource).toEqual(gen1DataSource);
expect(gen2Resource).toEqual(gen2Resource);
}
23 changes: 23 additions & 0 deletions packages/amplify-migration-codegen-e2e/src/function-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import path from 'node:path';
import * as fs from 'fs-extra';

export function copyFunctionFile(projRoot: string, gen1FunctionName: string) {
const sourcePath = path.join(
projRoot,
'.amplify',
'migration',
'amplify',
'backend',
'function',
gen1FunctionName.split('-')[0],
'src',
'index.js',
);
const destinationPath = path.join(projRoot, 'amplify', 'function', gen1FunctionName.split('-')[0], 'handler.ts');
const content = fs.readFileSync(sourcePath, 'utf8');

// Replace the first occurrence of 'event' with 'event: any'
const modifiedContent = content.replace(/(exports\.handler\s*=\s*async\s*\(\s*)event(\s*\))/, '$1event: any$2');

fs.writeFileSync(destinationPath, modifiedContent, 'utf8');
}
Loading

0 comments on commit d310c32

Please sign in to comment.