Skip to content

Commit

Permalink
test: v1 to v2 graphql migration test package (#8595)
Browse files Browse the repository at this point in the history
* test: add new v2 schema migrator package

* chore: rename package

* chore: more renaming

* test: use real migrator in test suite
  • Loading branch information
edwardfoyle authored Nov 2, 2021
1 parent 2cb2f4d commit 1ba9318
Show file tree
Hide file tree
Showing 13 changed files with 399 additions and 34 deletions.
66 changes: 66 additions & 0 deletions packages/amplify-graphql-migration-tests/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"name": "amplify-graphql-migration-tests",
"version": "1.0.0",
"description": "Tests migration from v1 to v2 of the Amplify GraphQL transformer",
"main": "lib/index.js",
"private": true,
"scripts": {
"test": "jest"
},
"repository": {
"type": "git",
"url": "https://github.com/aws-amplify/amplify-cli.git",
"directory": "packages/amplify-schema-migrator-tests"
},
"keywords": [
"graphql",
"transformer",
"migration",
"test"
],
"author": "Amazon Web Services",
"license": "Apache-2.0",
"jest": {
"collectCoverage": true,
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
"testURL": "http://localhost",
"testRegex": "((\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
]
},
"devDependencies": {
"@aws-amplify/graphql-auth-transformer": "0.1.0",
"@aws-amplify/graphql-default-value-transformer": "0.2.0",
"@aws-amplify/graphql-function-transformer": "0.4.5",
"@aws-amplify/graphql-http-transformer": "0.5.5",
"@aws-amplify/graphql-index-transformer": "0.4.0",
"@aws-amplify/graphql-model-transformer": "0.6.4",
"@aws-amplify/graphql-predictions-transformer": "0.3.5",
"@aws-amplify/graphql-relational-transformer": "0.3.1",
"@aws-amplify/graphql-searchable-transformer": "0.6.3",
"@aws-amplify/graphql-transformer-core": "0.9.2",
"@aws-amplify/graphql-transformer-interfaces": "1.10.0",
"@aws-amplify/graphql-transformer-migrator": "0.1.0",
"@aws-cdk/cloudformation-diff": "~1.124.0",
"amplify-prompts":"1.2.0",
"fs-extra": "^8.1.0",
"graphql-auth-transformer": "6.24.26",
"graphql-connection-transformer": "4.21.25",
"graphql-dynamodb-transformer": "6.22.25",
"graphql-elasticsearch-transformer": "4.12.5",
"graphql-function-transformer": "2.5.24",
"graphql-http-transformer": "4.18.12",
"graphql-key-transformer": "2.23.25",
"graphql-predictions-transformer": "2.5.24",
"graphql-transformer-core": "6.30.2",
"graphql-versioned-transformer": "4.17.25"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { getTestCaseRegistry } from '../test-case-registry';
import { v1transformerProvider } from '../v1-transformer-provider';
import { v2transformerProvider } from '../v2-transformer-provider';
import { migrateSchema } from '../migrate-schema-wrapper';
import { getNestedStackDiffRules } from '../nested-stack-diff-rules';
import * as cdkDiff from '@aws-cdk/cloudformation-diff';
import { ResourceImpact } from '@aws-cdk/cloudformation-diff';

describe('v1 to v2 migration', () => {
test.concurrent.each(getTestCaseRegistry())(
`validate $name schema migration`,
async ({ name, schema, v1TransformerConfig, v2TransformerConfig }) => {
console.log(name); // for some reason name substitution isn't working in the test title, so printing it here
// run v1 transformer
const v1Transformer = v1transformerProvider(v1TransformerConfig);
const v1result = v1Transformer.transform(schema);

// migrate schema from v1 to v2
const migratedSchema = await migrateSchema(schema);

// run v2 transformer
const v2transformer = v2transformerProvider(v2TransformerConfig);
const v2result = v2transformer.transform(migratedSchema);

// get initial nested stack names
// TODO will probably have to update the logic a bit if we want to test @searchable migrations here as that will create a SearchableStack that we need to account for
const v1nestedStackNames = Object.keys(v1result.stacks).filter(stackName => stackName !== 'ConnectionStack'); // The v1 transformer puts all connection resolvers in a 'ConnectionStack'. This stack does not defined any data resources

// verify root stack diff
const diff = cdkDiff.diffTemplate(v1result.rootStack, v2result.rootStack);
v1nestedStackNames.forEach(stackName => {
expect([ResourceImpact.WILL_UPDATE, ResourceImpact.NO_CHANGE]).toContain(diff.resources.changes[stackName].changeImpact);
});

// verify nested stack diffs
const nestedStackDiffRules = getNestedStackDiffRules();
v1nestedStackNames.forEach(stackName => {
const nestedStackDiff = cdkDiff.diffTemplate(v1result.stacks[stackName], v2result.stacks[stackName]);
nestedStackDiffRules.forEach(rule => rule(stackName, nestedStackDiff));
});
},
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const featureFlagProviderStub = {
getBoolean: () => true,
getString: () => '',
getNumber: () => 0,
getObject: () => ({}),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { runMigration } from '@aws-amplify/graphql-transformer-migrator';
import * as fs from 'fs-extra';
import { prompter } from 'amplify-prompts';

jest.mock('fs-extra');
jest.mock('amplify-prompts');

const fs_mock = fs as jest.Mocked<typeof fs>;
const prompter_mock = prompter as jest.Mocked<typeof prompter>;

export type AuthMode = 'apiKey' | 'iam' | 'userPools' | 'oidc';

export const migrateSchema = async (schema: string, authMode: AuthMode = 'apiKey'): Promise<string> => {
fs_mock.writeFile.mockClear();
prompter_mock.pick.mockResolvedValue('Yes');
await runMigration([{ schema, filePath: 'testPath' }], authMode);
const transformedSchema = fs_mock.writeFile.mock.calls[0][1];
expect(typeof transformedSchema).toBe('string');
return transformedSchema;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { ResourceImpact, TemplateDiff } from '@aws-cdk/cloudformation-diff';

export const getNestedStackDiffRules = (): NestedStackDiffRule[] => [
onlyUpdatesTableNameProperty,
tableNameResolvesToSameName,
dataSourceLogicalIdsAreSame,
];

/**
* The table name is not actually updated but the way it's defined is changed (tableNameResolvesToSameName will check the table name)
* This check asserts that no other table properties were changed by the migration (GSIs, LSIs, etc)
* @param stackName The name of the nested stack
* @param diff The diff of the nested stack
*/
const onlyUpdatesTableNameProperty = (stackName: string, diff: TemplateDiff) => {
const propertyUpdates = diff.resources.changes[`${stackName}Table`].propertyUpdates;
try {
expect(Object.keys(propertyUpdates)).toEqual(['TableName']); // The table name should resolve to the same value but the way it's defined is different so it shows up here as a diff
} catch (err) {
console.error(`Expected only TableName update for table ${stackName}Table. Instead got updates:`);
console.log(JSON.stringify(propertyUpdates, undefined, 2));
throw err;
}
};

const tableNameResolvesToSameName = (stackName: string, diff: TemplateDiff) => {
const propertyUpdates = diff.resources.changes[`${stackName}Table`].propertyUpdates;
const newTableName = propertyUpdates.TableName.newValue;
expect(newTableName['Fn::Join']).toBeDefined();
const joinParams = newTableName['Fn::Join'];
const joinStr = joinParams[0] as string;
const joinElements = joinParams[1] as any[];

const apiId = 'testApiId';
const env = 'testEnv';

const replacedElements = joinElements.map(el => {
if (typeof el?.Ref === 'string') {
if (el.Ref.startsWith('referencetotransformerrootstackGraphQLAPI')) {
return apiId;
}
if (el.Ref.startsWith('referencetotransformerrootstackenv')) {
return env;
}
}
return el;
});
const finalTableName = replacedElements.join(joinStr);
expect(finalTableName).toEqual(`${stackName}-${apiId}-${env}`);
};

const dataSourceLogicalIdsAreSame = (_: string, diff: TemplateDiff) => {
const areDataSourcesReplaced = Object.values(diff.resources.changes)
.filter(diff => diff.resourceType === 'AWS::AppSync::DataSource')
.map(diff => diff.changeImpact === ResourceImpact.WILL_REPLACE)
.reduce((acc, it) => acc && it, true);
expect(areDataSourcesReplaced).toBe(true);
};

export type NestedStackDiffRule = (stackName: string, diff: TemplateDiff) => void;
46 changes: 46 additions & 0 deletions packages/amplify-graphql-migration-tests/src/test-case-registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { TestEntry } from './test-case-types';

/**
* REGISTER TEST CASES HERE
*/
export const getTestCaseRegistry = (): TestEntry[] => [
{
name: 'basic bi-di has-one and has-many connection',
schema: basicBiDiRelationSchema,
},
/*
{
name: 'add additional tests with a descriptive name',
schema: schemaName // define the schema below and reference it here. If the schema is exceptionally large, consider importing it from another file
v1TransformerConfig: // if the tests needs custom v1 transformer config, include it here (the framework currently has limited support for additional config so you may need to make updates)
v2TransformerConfig: // same for v2 transformer
}
*/
];

/*
DEFINE TEST SCHEMAS BELOW
*/

const basicBiDiRelationSchema = /* GraphQL */ `
type Blog @model {
id: ID!
name: String!
postID: ID
post: Post @connection(fields: ["postID"])
}
type Post @model {
id: ID!
title: String!
blogID: ID!
blog: Blog @connection(fields: ["blogID"])
comments: [Comment] @connection(keyName: "byPost", fields: ["id"])
}
type Comment @model @key(name: "byPost", fields: ["postID"]) {
id: ID!
postID: ID!
post: Post @connection(fields: ["postID"])
}
`;
20 changes: 20 additions & 0 deletions packages/amplify-graphql-migration-tests/src/test-case-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { TransformerPluginProvider } from '@aws-amplify/graphql-transformer-interfaces';
import { ITransformer } from 'graphql-transformer-core';

// Defines a single test input
export type TestEntry = {
name: string;
schema: string;
v1TransformerConfig?: Partial<V1TransformerTestConfig>;
v2TransformerConfig?: Partial<V2TransformerTestConfig>;
};

// If we need to vary other transformer config per test we can add additional parameters here
export type V1TransformerTestConfig = {
transformers: ITransformer[];
};

// If we need to vary other transformer config per test we can add additional parameters here
export type V2TransformerTestConfig = {
transformers: TransformerPluginProvider[];
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { DynamoDBModelTransformer } from 'graphql-dynamodb-transformer';
import { ModelConnectionTransformer } from 'graphql-connection-transformer';
import { VersionedModelTransformer } from 'graphql-versioned-transformer';
import { HttpTransformer } from 'graphql-http-transformer';
import { KeyTransformer } from 'graphql-key-transformer';
import { GraphQLTransform } from 'graphql-transformer-core';
import { featureFlagProviderStub } from './feature-flag-stub';
import { V1TransformerTestConfig } from './test-case-types';

const defaultConfig: V1TransformerTestConfig = {
transformers: [
new DynamoDBModelTransformer(),
new VersionedModelTransformer(),
new HttpTransformer(),
new KeyTransformer(),
new ModelConnectionTransformer(),
],
};

export const v1transformerProvider = (config: Partial<V1TransformerTestConfig> = {}): GraphQLTransform => {
const subbedConfig: V1TransformerTestConfig = { ...defaultConfig, ...config };

return new GraphQLTransform({
transformers: subbedConfig.transformers,
transformConfig: {
Version: 5,
},
featureFlags: featureFlagProviderStub,
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { GraphQLTransform } from '@aws-amplify/graphql-transformer-core';
import { ModelTransformer } from '@aws-amplify/graphql-model-transformer';
import { AuthTransformer } from '@aws-amplify/graphql-auth-transformer';
import { FunctionTransformer } from '@aws-amplify/graphql-function-transformer';
import { HttpTransformer } from '@aws-amplify/graphql-http-transformer';
import { IndexTransformer, PrimaryKeyTransformer } from '@aws-amplify/graphql-index-transformer';
import {
BelongsToTransformer,
HasManyTransformer,
HasOneTransformer,
ManyToManyTransformer,
} from '@aws-amplify/graphql-relational-transformer';
import { DefaultValueTransformer } from '@aws-amplify/graphql-default-value-transformer';
import { TransformerPluginProvider, AppSyncAuthConfiguration } from '@aws-amplify/graphql-transformer-interfaces';
import { featureFlagProviderStub } from './feature-flag-stub';
import { V2TransformerTestConfig } from './test-case-types';

export const v2transformerProvider = (config: Partial<V2TransformerTestConfig> = {}): GraphQLTransform => {
const transform = new GraphQLTransform({
transformers: config.transformers ?? getDefaultTransformers(),
authConfig: defaultAuthConfig,
featureFlags: featureFlagProviderStub,
sandboxModeEnabled: true,
});

return transform;
};

const getDefaultTransformers = () => {
const modelTransformer = new ModelTransformer();
const indexTransformer = new IndexTransformer();
const hasOneTransformer = new HasOneTransformer();

const authTransformer = new AuthTransformer();
const transformers: TransformerPluginProvider[] = [
modelTransformer,
new FunctionTransformer(),
new HttpTransformer(),
new PrimaryKeyTransformer(),
indexTransformer,
new BelongsToTransformer(),
new HasManyTransformer(),
hasOneTransformer,
new ManyToManyTransformer(modelTransformer, indexTransformer, hasOneTransformer, authTransformer),
new DefaultValueTransformer(),
authTransformer,
];

return transformers;
};

const defaultAuthConfig: AppSyncAuthConfiguration = {
defaultAuthentication: {
authenticationType: 'API_KEY',
},
additionalAuthenticationProviders: [],
};
11 changes: 11 additions & 0 deletions packages/amplify-graphql-migration-tests/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./lib",
"rootDir": "./src",
},
"references": [
{"path": "../amplify-function-plugin-interface"},
{"path": "../graphql-transformer-core"},
]
}
2 changes: 1 addition & 1 deletion packages/amplify-graphql-transformer-migrator/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { attemptV2TransformerMigration } from './schema-migrator';
export { attemptV2TransformerMigration, runMigration } from './schema-migrator';
Loading

0 comments on commit 1ba9318

Please sign in to comment.