From 4d44c6c716077d1948b2083127626c557d17d2c7 Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Tue, 12 Sep 2023 11:22:43 -0600 Subject: [PATCH] feat: codegen add --region (#683) --- .../amplify-codegen/commands/codegen/add.js | 10 +- packages/amplify-codegen/package.json | 2 +- packages/amplify-codegen/src/commands/add.js | 6 +- packages/amplify-codegen/src/constants.js | 9 +- .../amplify-codegen/tests/cli/add.test.js | 144 ++++++++++++++++++ .../commands/__snapshots__/add.test.js.snap | 37 +++++ .../tests/commands/add.test.js | 83 ++++++++++ 7 files changed, 280 insertions(+), 11 deletions(-) create mode 100644 packages/amplify-codegen/tests/cli/add.test.js create mode 100644 packages/amplify-codegen/tests/commands/__snapshots__/add.test.js.snap diff --git a/packages/amplify-codegen/commands/codegen/add.js b/packages/amplify-codegen/commands/codegen/add.js index 89ac18581..4a7d8c348 100644 --- a/packages/amplify-codegen/commands/codegen/add.js +++ b/packages/amplify-codegen/commands/codegen/add.js @@ -9,14 +9,16 @@ module.exports = { try { const { options = {} } = context.parameters; const keys = Object.keys(options); - if (keys.length && !keys.includes('apiId')) { - const paramMsg = keys.length > 1 ? 'Invalid parameters ' : 'Invalid parameter '; + // frontend and framework are undocumented, but are read when apiId is also supplied + const { apiId = null, region, yes, frontend, framework, ...rest } = options; + const extraOptions = Object.keys(rest); + if (extraOptions.length) { + const paramMsg = extraOptions.length > 1 ? 'Invalid parameters' : 'Invalid parameter'; context.print.info(`${paramMsg} ${keys.join(', ')}`); context.print.info(constants.INFO_MESSAGE_ADD_ERROR); return; } - const apiId = context.parameters.options.apiId || null; - await codeGen.add(context, apiId); + await codeGen.add(context, apiId, region); } catch (ex) { context.print.error(ex.message); } diff --git a/packages/amplify-codegen/package.json b/packages/amplify-codegen/package.json index a884215e3..e26f87778 100644 --- a/packages/amplify-codegen/package.json +++ b/packages/amplify-codegen/package.json @@ -46,7 +46,7 @@ "coverageThreshold": { "global": { "branches": 54, - "functions": 65, + "functions": 64, "lines": 72 } }, diff --git a/packages/amplify-codegen/src/commands/add.js b/packages/amplify-codegen/src/commands/add.js index 713e3194b..c81546d66 100644 --- a/packages/amplify-codegen/src/commands/add.js +++ b/packages/amplify-codegen/src/commands/add.js @@ -1,4 +1,5 @@ const Ora = require('ora'); +const process = require('process'); const { loadConfig } = require('../codegen-config'); const constants = require('../constants'); const generateStatements = require('./statements'); @@ -23,11 +24,11 @@ const askForFramework = require('../walkthrough/questions/selectFramework'); const frontends = ['android', 'ios', 'javascript']; const frameworks = ['angular', 'ember', 'ionic', 'react', 'react-native', 'vue', 'none']; -async function add(context, apiId = null) { +async function add(context, apiId = null, region = 'us-east-1') { let withoutInit = false; // Determine if working in an amplify project try { - context.amplify.getProjectMeta(); + await context.amplify.getProjectMeta(); } catch (e) { withoutInit = true; const config = loadConfig(context, withoutInit); @@ -69,7 +70,6 @@ async function add(context, apiId = null) { } } - let region = 'us-east-1'; if (!withoutInit) { region = getProjectAwsRegion(context); } diff --git a/packages/amplify-codegen/src/constants.js b/packages/amplify-codegen/src/constants.js index 3a6cb077d..111122e6e 100644 --- a/packages/amplify-codegen/src/constants.js +++ b/packages/amplify-codegen/src/constants.js @@ -19,7 +19,8 @@ module.exports = { PROMPT_MSG_SELECT_PROJECT: 'Choose the AppSync API', PROMPT_MSG_SELECT_REGION: 'Choose AWS Region', ERROR_CODEGEN_TARGET_NOT_SUPPORTED: 'is not supported by codegen plugin', - ERROR_FLUTTER_CODEGEN_NOT_SUPPORTED: 'Flutter only supports the command $amplify codegen models. All the other codegen commands are not supported.', + ERROR_FLUTTER_CODEGEN_NOT_SUPPORTED: + 'Flutter only supports the command $amplify codegen models. All the other codegen commands are not supported.', ERROR_CODEGEN_FRONTEND_NOT_SUPPORTED: 'The project frontend is not supported by codegen', ERROR_MSG_MAX_DEPTH: 'Depth should be a integer greater than 0', ERROR_CODEGEN_NO_API_AVAILABLE: 'There are no GraphQL APIs available.\nAdd by running $amplify api add', @@ -37,7 +38,8 @@ module.exports = { CMD_DESCRIPTION_CONFIGURE: 'Change/Update codegen configuration', ERROR_CODEGEN_NO_API_CONFIGURED: 'code generation is not configured. Configure it by running \n$amplify codegen add', ERROR_CODEGEN_PENDING_API_PUSH: 'AppSync API is not pushed to the cloud. Did you forget to do \n$amplify api push', - ERROR_CODEGEN_NO_API_META: 'Cannot find API metadata. Please reset codegen by running $amplify codegen remove && amplify codegen add --apiId YOUR_API_ID', + ERROR_CODEGEN_NO_API_META: + 'Cannot find API metadata. Please reset codegen by running $amplify codegen remove && amplify codegen add --apiId YOUR_API_ID', WARNING_CODEGEN_PENDING_API_PUSH: 'The APIs listed below are not pushed to the cloud. Run amplify api push', ERROR_APPSYNC_API_NOT_FOUND: 'Could not find the AppSync API. If you have removed the AppSync API in the console run amplify codegen remove', @@ -55,5 +57,6 @@ module.exports = { INFO_MESSAGE_DOWNLOAD_ERROR: 'Downloading schema failed', INFO_MESSAGE_OPS_GEN: 'Generating GraphQL operations', INFO_MESSAGE_OPS_GEN_SUCCESS: 'Generated GraphQL operations successfully and saved at ', - INFO_MESSAGE_ADD_ERROR: 'amplify codegen add takes only apiId as parameter. \n$ amplify codegen add [--apiId ]', + INFO_MESSAGE_ADD_ERROR: + 'amplify codegen add takes only apiId and region as parameters. \n$ amplify codegen add [--apiId ] [--region ]', }; diff --git a/packages/amplify-codegen/tests/cli/add.test.js b/packages/amplify-codegen/tests/cli/add.test.js new file mode 100644 index 000000000..5a460920f --- /dev/null +++ b/packages/amplify-codegen/tests/cli/add.test.js @@ -0,0 +1,144 @@ +const add = require('../../commands/codegen/add'); +const codeGen = require('../../src/index'); + +jest.mock('../../src/index'); + +describe('cli - add', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + test('feature name', () => { + expect(add.name).toEqual('add'); + }); + + describe('run', () => { + test('executes codegen add', async () => { + const context = { + parameters: {}, + }; + await add.run(context); + expect(codeGen.add).toHaveBeenCalledWith(context, null, undefined); + }); + + test('catches error in codegen add', async () => { + const error = jest.fn(); + const context = { + parameters: {}, + print: { + error, + }, + }; + + const codegenError = new Error('failed to read file'); + codeGen.add.mockRejectedValueOnce(codegenError); + await add.run(context); + expect(error).toHaveBeenCalledWith(codegenError.message); + }); + + test('passes apiId', async () => { + const apiId = 'apiid'; + const context = { + parameters: { + options: { + apiId, + }, + }, + }; + await add.run(context); + expect(codeGen.add).toHaveBeenCalledWith(context, apiId, undefined); + }); + + test('passes region', async () => { + const region = 'region'; + const context = { + parameters: { + options: { + region, + }, + }, + }; + await add.run(context); + expect(codeGen.add).toHaveBeenCalledWith(context, null, region); + }); + + test('throws error on invalid arg', async () => { + const badArg = 'badArg'; + const info = jest.fn(); + const context = { + parameters: { + options: { + badArg, + }, + }, + print: { + info, + }, + }; + await add.run(context); + expect(info).toHaveBeenCalledWith('Invalid parameter badArg'); + + expect(info).toHaveBeenCalledWith( + 'amplify codegen add takes only apiId and region as parameters. \n$ amplify codegen add [--apiId ] [--region ]', + ); + }); + + test('throws error on invalid args', async () => { + const badArgOne = 'badArgOne'; + const badArgTwo = 'badArgTwo'; + const info = jest.fn(); + const context = { + parameters: { + options: { + badArgOne, + badArgTwo, + }, + }, + print: { + info, + }, + }; + await add.run(context); + expect(info).toHaveBeenCalledWith('Invalid parameters badArgOne, badArgTwo'); + + expect(info).toHaveBeenCalledWith( + 'amplify codegen add takes only apiId and region as parameters. \n$ amplify codegen add [--apiId ] [--region ]', + ); + }); + + test('allows undocummented frontend and framework', async () => { + const frontend = 'frontend'; + const framework = 'framework'; + const info = jest.fn(); + const context = { + parameters: { + options: { + frontend, + framework, + }, + }, + print: { + info, + }, + }; + await add.run(context); + expect(info).not.toHaveBeenCalled(); + }); + + test('ignores yes arg', async () => { + const yes = true; + const info = jest.fn(); + const context = { + parameters: { + options: { + yes, + }, + }, + print: { + info, + }, + }; + await add.run(context); + }); + }); +}); diff --git a/packages/amplify-codegen/tests/commands/__snapshots__/add.test.js.snap b/packages/amplify-codegen/tests/commands/__snapshots__/add.test.js.snap new file mode 100644 index 000000000..5cbd750a3 --- /dev/null +++ b/packages/amplify-codegen/tests/commands/__snapshots__/add.test.js.snap @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`command - add without init should read frontend and framework from options 1`] = ` +Object { + "amplifyExtension": Object { + "apiId": "MOCK_API_ID", + "codeGenTarget": "TYPE_SCRIPT_OR_FLOW_OR_ANY_OTHER_LANGUAGE", + "docsFilePath": "MOCK_DOCS_FILE_PATH", + "framework": "vue", + "frontend": "javascript", + "generatedFileName": "API.TS", + "region": "us-east-1", + }, + "excludes": "MOCK_EXCLUDE", + "includes": "MOCK_INCLUDE", + "projectName": "Codegen Project", + "schema": "/user/foo/project/schema.graphql", +} +`; + +exports[`command - add without init should use region supplied when without init 1`] = ` +Object { + "amplifyExtension": Object { + "apiId": "MOCK_API_ID", + "codeGenTarget": "TYPE_SCRIPT_OR_FLOW_OR_ANY_OTHER_LANGUAGE", + "docsFilePath": "MOCK_DOCS_FILE_PATH", + "framework": "react", + "frontend": "javascript", + "generatedFileName": "API.TS", + "region": "us-west-2", + }, + "excludes": "MOCK_EXCLUDE", + "includes": "MOCK_INCLUDE", + "projectName": "Codegen Project", + "schema": "/user/foo/project/schema.graphql", +} +`; diff --git a/packages/amplify-codegen/tests/commands/add.test.js b/packages/amplify-codegen/tests/commands/add.test.js index 5e921f4f3..b1e7a1f40 100644 --- a/packages/amplify-codegen/tests/commands/add.test.js +++ b/packages/amplify-codegen/tests/commands/add.test.js @@ -1,7 +1,10 @@ +const fs = require('fs'); const { loadConfig } = require('../../src/codegen-config'); const generateStatements = require('../../src/commands/statements'); const generateTypes = require('../../src/commands/types'); const addWalkthrough = require('../../src/walkthrough/add'); +const askForFrontend = require('../../src/walkthrough/questions/selectFrontend'); +const askForFramework = require('../../src/walkthrough/questions/selectFramework'); const changeAppSyncRegions = require('../../src/walkthrough/changeAppSyncRegions'); const { AmplifyCodeGenAPINotFoundError } = require('../../src/errors'); @@ -16,13 +19,22 @@ const MOCK_CONTEXT = { amplify: { getProjectMeta: jest.fn(), }, + parameters: { + options: {}, + }, }; +jest.mock('fs'); jest.mock('../../src/walkthrough/add'); +jest.mock('../../src/walkthrough/questions/selectFrontend'); +jest.mock('../../src/walkthrough/questions/selectFramework'); jest.mock('../../src/walkthrough/changeAppSyncRegions'); jest.mock('../../src/commands/types'); jest.mock('../../src/commands/statements'); jest.mock('../../src/codegen-config'); jest.mock('../../src/utils'); +jest.mock('process', () => ({ + cwd: () => '/user/foo/project', +})); const MOCK_INCLUDE_PATTERN = 'MOCK_INCLUDE'; const MOCK_EXCLUDE_PATTERN = 'MOCK_EXCLUDE'; @@ -148,4 +160,75 @@ describe('command - add', () => { await add(MOCK_CONTEXT); expect(generateTypes).not.toHaveBeenCalled(); }); + + it('should ignore region supplied when with init', async () => { + const region = 'us-west-2'; + await add(MOCK_CONTEXT, MOCK_API_ID, region); + expect(getProjectAwsRegion).toHaveBeenCalled(); + expect(getAppSyncAPIInfo).toHaveBeenCalledWith(MOCK_CONTEXT, MOCK_API_ID, MOCK_AWS_REGION); + }); + + describe('without init', () => { + const getProjectMeta = jest.fn(); + beforeEach(() => { + loadConfig.mockReturnValue({ ...LOAD_CONFIG_METHODS, getProjects: jest.fn().mockReturnValue([]) }); + askForFrontend.mockReturnValue('javascript'); + askForFramework.mockReturnValue('react'); + getProjectMeta.mockRejectedValue('no init'); + fs.existsSync.mockReturnValue(true); + }); + + afterEach(() => { + loadConfig.mockReset(); + askForFrontend.mockReset(); + askForFramework.mockReset(); + getProjectMeta.mockReset(); + fs.existsSync.mockReset(); + }); + + it('should read frontend and framework from options', async () => { + const parameters = { + options: { + frontend: 'javascript', + framework: 'vue', + }, + }; + await add({ ...MOCK_CONTEXT, amplify: { getProjectMeta }, parameters }, MOCK_API_ID); + expect(askForFrontend).not.toHaveBeenCalled(); + expect(askForFramework).not.toHaveBeenCalled(); + expect(LOAD_CONFIG_METHODS.addProject).toHaveBeenCalled(); + expect(LOAD_CONFIG_METHODS.addProject.mock.calls[0][0]).toMatchSnapshot(); + }); + + it('should use region supplied when without init', async () => { + const region = 'us-west-2'; + await add({ ...MOCK_CONTEXT, amplify: { getProjectMeta } }, MOCK_API_ID, region); + expect(getProjectAwsRegion).not.toHaveBeenCalled(); + expect(LOAD_CONFIG_METHODS.addProject).toHaveBeenCalled(); + expect(LOAD_CONFIG_METHODS.addProject.mock.calls[0][0]).toMatchSnapshot(); + }); + + it('should error on invalid frontend', () => { + const parameters = { + options: { + frontend: 'foo', + }, + }; + expect(add({ ...MOCK_CONTEXT, amplify: { getProjectMeta }, parameters }, MOCK_API_ID)).rejects.toThrowError( + 'Invalid frontend provided', + ); + }); + + it('should error on invalid framework', () => { + const parameters = { + options: { + frontend: 'javascript', + framework: 'foo', + }, + }; + expect(add({ ...MOCK_CONTEXT, amplify: { getProjectMeta }, parameters }, MOCK_API_ID)).rejects.toThrowError( + 'Invalid framework provided', + ); + }); + }); });