Skip to content

Commit

Permalink
fix: warn about multiple schema files in admin modelgen task (#12673)
Browse files Browse the repository at this point in the history
* fix: warn about multiple schema files in admin modelgen task

* chore: lint fix

* fix: add check for studio enabled projects

* chore: update snapshot
  • Loading branch information
phani-srikar committed Jun 17, 2023
1 parent ec098ca commit 4bc01ea
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { $TSContext, stateManager, pathManager } from '@aws-amplify/amplify-cli-
import * as fs from 'fs-extra';
import { adminModelgen } from '../admin-modelgen';
import { S3 } from '../aws-utils/aws-s3';
import { printer } from '@aws-amplify/amplify-prompts';

jest.mock('fs-extra');
jest.mock('../aws-utils/aws-s3');
jest.mock('@aws-amplify/amplify-cli-core');
jest.mock('graphql-transformer-core');
jest.mock('../utils/admin-helpers');
jest.mock('@aws-amplify/amplify-prompts');

const fsMock = fs as jest.Mocked<typeof fs>;
const stateManagerMock = stateManager as jest.Mocked<typeof stateManager>;
Expand Down Expand Up @@ -35,6 +36,10 @@ fsMock.createWriteStream.mockReturnValue({
},
} as unknown as fs.WriteStream);
fsMock.createReadStream.mockImplementation((filePath) => `mock body of ${filePath}` as unknown as fs.ReadStream);
jest.mock('../utils/admin-helpers', () => ({
...jest.requireActual('../utils/admin-helpers'),
isAmplifyAdminApp: jest.fn().mockResolvedValue({ isAdminApp: true }),
}));

const s3FactoryMock = S3 as jest.Mocked<typeof S3>;

Expand All @@ -60,65 +65,90 @@ const invokePluginMock = jest.fn();

let contextStub: $TSContext;

beforeEach(() => {
jest.clearAllMocks();
contextStub = {
amplify: {
invokePluginMethod: invokePluginMock,
},
} as unknown as $TSContext;
});

it('invokes codegen functions and writes assets to S3', async () => {
await adminModelgen(contextStub, resources);

expect(invokePluginMock.mock.calls.length).toBe(2);
expect(invokePluginMock.mock.calls[0][3]).toBe('generateModels');
expect(invokePluginMock.mock.calls[1][3]).toBe('generateModelIntrospection');
expect(s3Mock.uploadFile.mock.calls).toMatchInlineSnapshot(`
[
[
{
"Body": "mock body of mock/resource/dir/path/schema.graphql",
"Key": "models/testApiName/schema.graphql",
},
false,
],
[
{
"Body": "mock body of mock/project/root/amplify-codegen-temp/models/schema.js",
"Key": "models/testApiName/schema.js",
},
false,
],
[
{
"Body": "mock body of mock/project/root/amplify-codegen-temp/model-introspection.json",
"Key": "models/testApiName/modelIntrospection.json",
},
false,
],
]
`);
});

it('resets js config on error', async () => {
invokePluginMock.mockRejectedValue(new Error('test error'));
await expect(() => adminModelgen(contextStub, resources)).rejects.toThrowErrorMatchingInlineSnapshot(`"test error"`);
expect(stateManagerMock.setProjectConfig.mock.calls.length).toBe(2);
expect(stateManagerMock.setProjectConfig.mock.calls[1][1]).toBe(originalProjectConfig);
});

it('resets stdout on error', async () => {
const initialStdoutWriter = process.stdout.write;
invokePluginMock.mockRejectedValue(new Error('test error'));
await expect(() => adminModelgen(contextStub, resources)).rejects.toThrowErrorMatchingInlineSnapshot(`"test error"`);
expect(process.stdout.write).toBe(initialStdoutWriter);
describe('project with single schema file that exists', () => {
beforeEach(() => {
jest.clearAllMocks();
contextStub = {
amplify: {
invokePluginMethod: invokePluginMock,
},
} as unknown as $TSContext;
fsMock.existsSync.mockReturnValue(true);
});

it('invokes codegen functions and writes assets to S3', async () => {
await adminModelgen(contextStub, resources);

expect(invokePluginMock.mock.calls.length).toBe(2);
expect(invokePluginMock.mock.calls[0][3]).toBe('generateModels');
expect(invokePluginMock.mock.calls[1][3]).toBe('generateModelIntrospection');
expect(s3Mock.uploadFile.mock.calls).toMatchInlineSnapshot(`
[
[
{
"Body": "mock body of mock/resource/dir/path/schema.graphql",
"Key": "models/testApiName/schema.graphql",
},
false,
],
[
{
"Body": "mock body of mock/project/root/amplify-codegen-temp/models/schema.js",
"Key": "models/testApiName/schema.js",
},
false,
],
[
{
"Body": "mock body of mock/project/root/amplify-codegen-temp/model-introspection.json",
"Key": "models/testApiName/modelIntrospection.json",
},
false,
],
]
`);
});

it('resets js config on error', async () => {
invokePluginMock.mockRejectedValue(new Error('test error'));
await expect(() => adminModelgen(contextStub, resources)).rejects.toThrowErrorMatchingInlineSnapshot(`"test error"`);
expect(stateManagerMock.setProjectConfig.mock.calls.length).toBe(2);
expect(stateManagerMock.setProjectConfig.mock.calls[1][1]).toBe(originalProjectConfig);
});

it('resets stdout on error', async () => {
const initialStdoutWriter = process.stdout.write;
invokePluginMock.mockRejectedValue(new Error('test error'));
await expect(() => adminModelgen(contextStub, resources)).rejects.toThrowErrorMatchingInlineSnapshot(`"test error"`);
expect(process.stdout.write).toBe(initialStdoutWriter);
});

it('removes temp dir on error', async () => {
invokePluginMock.mockRejectedValue(new Error('test error'));
await expect(adminModelgen(contextStub, resources)).rejects.toThrowErrorMatchingInlineSnapshot(`"test error"`);
expect(fsMock.remove.mock.calls.length).toBe(1);
expect(fsMock.remove.mock.calls[0][0]).toMatchInlineSnapshot(`"mock/project/root/amplify-codegen-temp"`);
});
});

it('removes temp dir on error', async () => {
invokePluginMock.mockRejectedValue(new Error('test error'));
await expect(adminModelgen(contextStub, resources)).rejects.toThrowErrorMatchingInlineSnapshot(`"test error"`);
expect(fsMock.remove.mock.calls.length).toBe(1);
expect(fsMock.remove.mock.calls[0][0]).toMatchInlineSnapshot(`"mock/project/root/amplify-codegen-temp"`);
describe('studio enabled project with multiple schema files or non-existent single schema file', () => {
beforeEach(() => {
jest.clearAllMocks();
contextStub = {
amplify: {
invokePluginMethod: invokePluginMock,
},
} as unknown as $TSContext;
fsMock.existsSync.mockReturnValue(false);
});

it('early returns and throws appropriate warning for a studio app', async () => {
await adminModelgen(contextStub, resources);

expect(invokePluginMock.mock.calls.length).toBe(0);
expect(s3Mock.uploadFile.mock.calls.length).toBe(0);
expect(printer.warn).toBeCalledWith(
`Could not find the GraphQL schema file at \"mock/resource/dir/path/schema.graphql\". Amplify Studio's schema editor might not work as intended if you're using multiple schema files.`,
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import _ from 'lodash';
import * as path from 'path';
import { S3 } from './aws-utils/aws-s3';
import { ProviderName as providerName } from './constants';
import { printer } from '@aws-amplify/amplify-prompts';
import { isAmplifyAdminApp } from './utils/admin-helpers';

/**
* Generates DataStore Models for Admin UI CMS to consume
Expand All @@ -26,6 +28,18 @@ export const adminModelgen = async (context: $TSContext, resources: $TSAny[]): P
return;
}

const localSchemaPath = path.join(pathManager.getResourceDirectoryPath(undefined, 'api', resourceName), 'schema.graphql');
// Early return with a warning if the schema file does not exist
if (!fs.existsSync(localSchemaPath)) {
const { isAdminApp } = await isAmplifyAdminApp(appId);
if (isAdminApp) {
printer.warn(
`Could not find the GraphQL schema file at "${localSchemaPath}". Amplify Studio's schema editor might not work as intended if you're using multiple schema files.`,
);
}
return;
}

// the following is a hack to enable us to upload assets needed by studio CMS to the deployment bucket without
// calling AmplifyBackend.generateBackendAPIModels.
// Calling this API introduces a circular dependency because this API in turn executes the CLI to generate codegen assets
Expand Down Expand Up @@ -65,7 +79,6 @@ export const adminModelgen = async (context: $TSContext, resources: $TSAny[]): P
// invokes https://github.com/aws-amplify/amplify-codegen/blob/main/packages/amplify-codegen/src/commands/model-intropection.js#L8
await context.amplify.invokePluginMethod(context, 'codegen', undefined, 'generateModelIntrospection', [context]);

const localSchemaPath = path.join(pathManager.getResourceDirectoryPath(undefined, 'api', resourceName), 'schema.graphql');
const localSchemaJsPath = path.join(absoluteTempOutputDir, 'models', 'schema.js');
const localModelIntrospectionPath = path.join(absoluteTempOutputDir, 'model-introspection.json');

Expand Down

0 comments on commit 4bc01ea

Please sign in to comment.