Skip to content

Commit

Permalink
test: build type and graphql statement generation (#757)
Browse files Browse the repository at this point in the history
  • Loading branch information
dpilch authored Nov 27, 2023
1 parent d74bbd9 commit 3c363c6
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 29 deletions.
2 changes: 2 additions & 0 deletions .codebuild/e2e_workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ batch:
TEST_SUITE: >-
src/__tests__/build-app-ts.test.ts|src/__tests__/uninitialized-project-codegen-js.test.ts|src/__tests__/uninitialized-project-modelgen-android.test.ts|src/__tests__/uninitialized-project-modelgen-flutter.test.ts
CLI_REGION: ap-southeast-1
DISABLE_ESLINT_PLUGIN: true
depend-on:
- publish_to_local_registry
- identifier: l_uninitialized_project_modelgen_ios_uninitialized_project_modelgen_js
Expand Down Expand Up @@ -250,6 +251,7 @@ batch:
TEST_SUITE: >-
src/__tests__/build-app-ts.test.ts|src/__tests__/uninitialized-project-codegen-js.test.ts|src/__tests__/uninitialized-project-modelgen-android.test.ts|src/__tests__/uninitialized-project-modelgen-flutter.test.ts
CLI_REGION: us-east-1
DISABLE_ESLINT_PLUGIN: true
depend-on:
- publish_to_local_registry
- build_windows
Expand Down
29 changes: 29 additions & 0 deletions packages/amplify-codegen-e2e-core/src/categories/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,35 @@ export function addApiWithBlankSchemaAndConflictDetection(cwd: string) {
});
}

export function addApiWithDefaultSchemaAndConflictDetection(cwd: string) {
return new Promise<void>((resolve, reject) => {
spawn(getCLIPath(defaultOptions.testingWithLatestCodebase), ['add', 'api'], { cwd, stripColors: true })
.wait('Select from one of the below mentioned services:')
.sendCarriageReturn()
.wait(/.*Here is the GraphQL API that we will create. Select a setting to edit or continue.*/)
.sendKeyUp()
.sendCarriageReturn()
.wait(/.*Enable conflict detection.*/)
.sendConfirmYes()
.wait(/.*Select the default resolution strategy.*/)
.sendCarriageReturn()
.wait(/.*Here is the GraphQL API that we will create. Select a setting to edit or continue.*/)
.sendCarriageReturn()
.wait('Choose a schema template:')
.sendCarriageReturn()
.wait('Do you want to edit the schema now?')
.sendConfirmNo()
.wait('"amplify publish" will build all your local backend and frontend resources')
.run((err: Error) => {
if (!err) {
resolve();
} else {
reject(err);
}
});
});
}

export function updateApiSchema(cwd: string, projectName: string, schemaName: string, forceUpdate: boolean = false) {
const testSchemaPath = getSchemaPath(schemaName);
let schemaText = fs.readFileSync(testSchemaPath).toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,7 @@ export function initIosProjectWithProfile(cwd: string, settings: Object): Promis
.wait('Choose your default editor:')
.sendLine(s.editor)
.wait("Choose the type of app that you're building")
.sendKeyDown(3)
.sendCarriageReturn()
.sendLine('ios')
.wait('Select the authentication method you want to use:')
.sendCarriageReturn()
.wait('Please choose the profile you want to use')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,54 @@
import {
initProjectWithQuickstart,
initProjectWithProfile,
DEFAULT_ANDROID_CONFIG,
updateApiSchemaWithText,
addApiWithDefaultSchemaAndConflictDetection,
generateModels,
generateStatementsAndTypes,
androidBuild,
acceptLicenses
acceptLicenses,
addCodegen,
AmplifyFrontend,
apiGqlCompile,
} from '@aws-amplify/amplify-codegen-e2e-core';
const { schemas } = require('@aws-amplify/graphql-schema-test-library');
import { existsSync, writeFileSync, readdirSync, rmSync } from 'fs';
import { existsSync, writeFileSync, readdirSync, rmSync, readFileSync } from 'fs';
import path from 'path';
import { parse } from 'graphql';

const skip = new Set(['v2-primary-key-with-composite-sort-key', 'custom-@primaryKey-with-sort-fields']);
const skip = new Set([
'v2-primary-key-with-composite-sort-key',
'custom-@primaryKey-with-sort-fields',
'v2-cyclic-has-one-dependency',
'v2-cyclic-has-many-dependency',
]);

describe('build app - Android', () => {
let apiName: string;
const projectRoot = path.resolve('test-apps/android');
const config = DEFAULT_ANDROID_CONFIG;
const modelDir = 'app/src/main/java/com/amplifyframework/datastore/generated/model';
const statementsDir = 'app/src/main/graphql/com/amazonaws/amplify/generated/graphql';

beforeAll(async () => {
await initProjectWithQuickstart(projectRoot, { ...config });
await initProjectWithProfile(projectRoot, { ...config });
await addApiWithDefaultSchemaAndConflictDetection(projectRoot);
apiName = readdirSync(path.join(projectRoot, 'amplify', 'backend', 'api'))[0];
await apiGqlCompile(projectRoot);
await addCodegen(projectRoot, {
frontendType: AmplifyFrontend.android,
});
await acceptLicenses(projectRoot);
});

afterAll(async () => {
rmSync(path.join(projectRoot, 'amplify'), { recursive: true, force: true });
rmSync(path.join(projectRoot, '.graphqlconfig.yml'), { recursive: true, force: true });
});

afterEach(() => {
rmSync(path.join(projectRoot, modelDir), { recursive: true, force: true });
rmSync(path.join(projectRoot, statementsDir), { recursive: true, force: true });
});

Object.entries(schemas).forEach(([schemaName, schema]) => {
Expand All @@ -39,8 +58,14 @@ describe('build app - Android', () => {
// @ts-ignore
const schemaText = `input AMPLIFY { globalAuthRule: AuthRule = { allow: public } }\n${schema.sdl}`;
updateApiSchemaWithText(projectRoot, apiName, schemaText);
await apiGqlCompile(projectRoot);
await generateModels(projectRoot);
await generateStatementsAndTypes(projectRoot);
await androidBuild(projectRoot, { ...config });
// android uses raw graphql syntax
parse(readFileSync(path.join(projectRoot, path.join(statementsDir, 'queries.graphql')), 'utf8'));
parse(readFileSync(path.join(projectRoot, path.join(statementsDir, 'subscriptions.graphql')), 'utf8'));
parse(readFileSync(path.join(projectRoot, path.join(statementsDir, 'mutations.graphql')), 'utf8'));
};
if (skip.has(schemaName)) {
it.skip(testName, testFunction);
Expand All @@ -49,11 +74,38 @@ describe('build app - Android', () => {
}
});

it('fails build with syntax error', async () => {
// not supported with conflict resolution enabled
[
['v2-cyclic-has-one-dependency', schemas['v2-cyclic-has-one-dependency']],
['v2-cyclic-has-many-dependency', schemas['v2-cyclic-has-many-dependency']],
].forEach(([schemaName, schema]) => {
// @ts-ignore
it(`builds models with ${schemaName}: ${schema.description}`, async () => {
// @ts-ignore
const schemaText = `input AMPLIFY { globalAuthRule: AuthRule = { allow: public } }\n${schema.sdl}`;
updateApiSchemaWithText(projectRoot, apiName, schemaText);
await generateModels(projectRoot);
await androidBuild(projectRoot, { ...config });
});
});

it('fails build with syntax error in models', async () => {
// @ts-ignore
updateApiSchemaWithText(projectRoot, apiName, Object.values(schemas)[0].sdl);
await apiGqlCompile(projectRoot);
await generateModels(projectRoot);
await writeFileSync(path.join(projectRoot, modelDir, 'AmplifyModelProvider.java'), 'foo\nbar');
await expect(androidBuild(projectRoot, { ...config })).rejects.toThrowError();
});

it('fails build with syntax error in statements', async () => {
// @ts-ignore
updateApiSchemaWithText(projectRoot, apiName, Object.values(schemas)[0].sdl);
await apiGqlCompile(projectRoot);
await generateModels(projectRoot);
await generateStatementsAndTypes(projectRoot);
writeFileSync(path.join(projectRoot, statementsDir, 'mutations.graphql'), 'foo\nbar');
expect(() => parse(readFileSync(path.join(projectRoot, statementsDir, 'mutations.graphql'), 'utf8'))).toThrowError();
await androidBuild(projectRoot, { ...config });
});
});
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
import {
initProjectWithQuickstart,
initProjectWithQuickstart
initIosProjectWithProfile,
addApiWithDefaultSchemaAndConflictDetection,
DEFAULT_IOS_CONFIG,
updateApiSchemaWithText,
generateModels,
generateStatementsAndTypes,
addCodegen,
AmplifyFrontend,
apiGqlCompile,
} from '@aws-amplify/amplify-codegen-e2e-core';
const { schemas } = require('@aws-amplify/graphql-schema-test-library');
import { writeFileSync, readdirSync, readFileSync } from 'fs';
import { writeFileSync, readdirSync, readFileSync, rmSync, mkdirSync } from 'fs';
import { copySync } from 'fs-extra';
import path from 'path';
import { parse } from 'graphql';

const skip = new Set([
'v2-recursive-has-one-dependency',
'v2-cyclic-has-one-dependency',
'v2-cyclic-has-many-dependency',
'@hasOne-with-@belongsTo-with-implicit-parameters',
'@hasOne-with-@belongsTo-with-explicit-parameters',
]);
Expand All @@ -22,15 +31,37 @@ describe('build app - Swift', () => {
const config = DEFAULT_IOS_CONFIG;

beforeAll(async () => {
await initProjectWithQuickstart(projectRoot, { ...config });
await initIosProjectWithProfile(projectRoot, { ...config });
await addApiWithDefaultSchemaAndConflictDetection(projectRoot);
apiName = readdirSync(path.join(projectRoot, 'amplify', 'backend', 'api'))[0];
await apiGqlCompile(projectRoot);
await addCodegen(projectRoot, {
frontendType: AmplifyFrontend.ios,
});
projectPBXProjCache = readFileSync(path.join(projectRoot, 'swift.xcodeproj', 'project.pbxproj'));
});

afterEach(async () => {
writeFileSync(path.join(projectRoot, 'swift.xcodeproj', 'project.pbxproj'), projectPBXProjCache);
});

afterAll(async () => {
await rmSync(path.join(projectRoot, 'amplify'), { recursive: true, force: true });
rmSync(path.join(projectRoot, '.graphqlconfig.yml'), { recursive: true, force: true });

// generate models one more time
// files are used in a GitHub action to test compilation
// codebuild does not suport MacOS instances
await initProjectWithQuickstart(projectRoot, { ...config });
apiName = readdirSync(path.join(projectRoot, 'amplify', 'backend', 'api'))[0];
const [schemaName, schema] = Object.entries(schemas)[0];
const schemaText = `input AMPLIFY { globalAuthRule: AuthRule = { allow: public } }\n${(schema as any).sdl}`;
updateApiSchemaWithText(projectRoot, apiName, schemaText);
const schemaFolderName = schemaName.replace(/[^a-zA-Z0-9]/g, '');
const outputDir = path.join(projectRoot, 'amplify', 'generated', 'models', schemaFolderName);
await generateModels(projectRoot, outputDir);
});

Object.entries(schemas).forEach(([schemaName, schema]) => {
// @ts-ignore
const testName = `builds with ${schemaName}: ${schema.description}`;
Expand All @@ -40,8 +71,14 @@ describe('build app - Swift', () => {
// @ts-ignore
const schemaText = `input AMPLIFY { globalAuthRule: AuthRule = { allow: public } }\n${schema.sdl}`;
console.log(schemaText); // log so that ci does not timeout
updateApiSchemaWithText(projectRoot, 'amplifyDatasource', schemaText);
updateApiSchemaWithText(projectRoot, apiName, schemaText);
await apiGqlCompile(projectRoot);
await generateModels(projectRoot, outputDir);
await generateStatementsAndTypes(projectRoot);
// swift uses raw graphql syntax
parse(readFileSync(path.join(projectRoot, 'graphql/queries.graphql'), 'utf8'));
parse(readFileSync(path.join(projectRoot, 'graphql/subscriptions.graphql'), 'utf8'));
parse(readFileSync(path.join(projectRoot, 'graphql/mutations.graphql'), 'utf8'));
};
if (skip.has(schemaName)) {
it.skip(testName, testFunction);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,47 +1,89 @@
import {
initProjectWithProfile,
DEFAULT_JS_CONFIG,
addApiWithBlankSchemaAndConflictDetection,
addApiWithDefaultSchemaAndConflictDetection,
updateApiSchemaWithText,
generateModels,
generateStatementsAndTypes,
craInstall,
craBuild,
addCodegen,
AmplifyFrontend,
apiGqlCompile,
} from '@aws-amplify/amplify-codegen-e2e-core';
const { schemas } = require('@aws-amplify/graphql-schema-test-library');
import { existsSync, writeFileSync, readdirSync, rmSync } from 'fs';
import path from 'path';

const schema = 'simple_model.graphql';

// not supported with conflict resolution enabled
const skip = new Set(['v2-cyclic-has-one-dependency', 'v2-cyclic-has-many-dependency']);

describe('build app - JS', () => {
let apiName: string;
const projectRoot = path.resolve('test-apps/ts');
const config = DEFAULT_JS_CONFIG;

beforeAll(async () => {
await initProjectWithProfile(projectRoot, { ...config });
await addApiWithBlankSchemaAndConflictDetection(projectRoot);
await craInstall(projectRoot, { ...config });
await addApiWithDefaultSchemaAndConflictDetection(projectRoot);
apiName = readdirSync(path.join(projectRoot, 'amplify', 'backend', 'api'))[0];
await apiGqlCompile(projectRoot);
await addCodegen(projectRoot, {
frontendType: AmplifyFrontend.javascript,
});
await craInstall(projectRoot, { ...config });
});

afterAll(async () => {
await rmSync(path.join(projectRoot, 'amplify'), { recursive: true, force: true });
rmSync(path.join(projectRoot, '.graphqlconfig.yml'), { recursive: true, force: true });
});

Object.entries(schemas).forEach(([schemaName, schema]) => {
// @ts-ignore
it(`builds with ${schemaName}: ${schema.description}`, async () => {
// @ts-ignore
const schemaText = `input AMPLIFY { globalAuthRule: AuthRule = { allow: public } }\n${schema.sdl}`;
const testName = `builds with ${schemaName}: ${(schema as any).description}`;
const testFunction = async () => {
const schemaText = `input AMPLIFY { globalAuthRule: AuthRule = { allow: public } }\n${(schema as any).sdl}`;
updateApiSchemaWithText(projectRoot, apiName, schemaText);
await apiGqlCompile(projectRoot);
await generateModels(projectRoot);
await generateStatementsAndTypes(projectRoot);
await craBuild(projectRoot, { ...config });
});
};
if (skip.has(schemaName)) {
it.skip(testName, testFunction);
} else {
it(testName, testFunction);
}
});

it('fails build with syntax error', async () => {
it('fails build with syntax error in models', async () => {
const schemaText = `input AMPLIFY { globalAuthRule: AuthRule = { allow: public } }\n${(Object.values(schemas)[0] as any).sdl}`;
updateApiSchemaWithText(projectRoot, apiName, schemaText);
await apiGqlCompile(projectRoot);
await generateStatementsAndTypes(projectRoot);
await writeFileSync(path.join(projectRoot, 'src', 'models', 'index.d.ts'), 'foo\nbar');
await expect(craBuild(projectRoot, { ...config })).rejects.toThrowError();
});

it('fails build with syntax error in statements', async () => {
const schemaText = `input AMPLIFY { globalAuthRule: AuthRule = { allow: public } }\n${(Object.values(schemas)[0] as any).sdl}`;
updateApiSchemaWithText(projectRoot, apiName, schemaText);
await apiGqlCompile(projectRoot);
await generateModels(projectRoot);
await generateStatementsAndTypes(projectRoot);
await writeFileSync(path.join(projectRoot, 'src', 'graphql', 'queries.ts'), 'foo\nbar');
await expect(craBuild(projectRoot, { ...config })).rejects.toThrowError();
});

it('fails build with syntax error in types', async () => {
const schemaText = `input AMPLIFY { globalAuthRule: AuthRule = { allow: public } }\n${(Object.values(schemas)[0] as any).sdl}`;
updateApiSchemaWithText(projectRoot, apiName, schemaText);
await apiGqlCompile(projectRoot);
await generateModels(projectRoot);
await generateStatementsAndTypes(projectRoot);
await writeFileSync(path.join(projectRoot, 'src', 'API.ts'), 'foo\nbar');
await expect(craBuild(projectRoot, { ...config })).rejects.toThrowError();
});
});
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
src/models
src/models
src/graphql
src/API.ts
6 changes: 0 additions & 6 deletions packages/amplify-codegen-e2e-tests/test-apps/ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,6 @@
"serve": "serve build",
"set-schema": "node scripts/set-schema.js"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
Expand Down
10 changes: 9 additions & 1 deletion packages/amplify-codegen-e2e-tests/test-apps/ts/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { useEffect } from 'react';
import * as models from './models'; // import and use models to avoid tree shaking
// import and use to avoid tree shaking
import * as models from './models';
import * as queries from './graphql/queries';
import * as mutations from './graphql/mutations';
import * as subscriptions from './graphql/subscriptions';
// don't need to import API.ts becuase it is imported by graphql statements

function App() {
useEffect(() => {
console.log(models);
console.log(queries);
console.log(mutations);
console.log(subscriptions);
}, []);
return <div></div>;
}
Expand Down
Loading

0 comments on commit 3c363c6

Please sign in to comment.