diff --git a/packages/amplify-util-mock/src/__tests__/api/api.test.ts b/packages/amplify-util-mock/src/__tests__/api/api.test.ts index d4c4ad4a735..06a042340ca 100644 --- a/packages/amplify-util-mock/src/__tests__/api/api.test.ts +++ b/packages/amplify-util-mock/src/__tests__/api/api.test.ts @@ -1,10 +1,11 @@ import * as fs from 'fs-extra'; import * as path from 'path'; -import { $TSContext, AmplifyFault, pathManager } from '@aws-amplify/amplify-cli-core'; +import { $TSContext, AmplifyError, pathManager } from '@aws-amplify/amplify-cli-core'; import { APITest } from '../../api/api'; import * as lambdaInvoke from '../../api/lambda-invoke'; import { getMockSearchableTriggerDirectory } from '../../utils'; import { ConfigOverrideManager } from '../../utils/config-override'; +import { run } from '../../commands/mock/api'; jest.mock('@aws-amplify/amplify-cli-core', () => ({ ...(jest.requireActual('@aws-amplify/amplify-cli-core') as Record), @@ -16,6 +17,10 @@ jest.mock('@aws-amplify/amplify-cli-core', () => ({ FeatureFlags: { getNumber: jest.fn(), }, + stateManager: { + getLocalEnvInfo: jest.fn(), + localEnvInfoExists: jest.fn(), + }, })); jest.mock('amplify-dynamodb-simulator', () => jest.fn()); jest.mock('fs-extra'); @@ -66,10 +71,12 @@ describe('Test Mock API methods', () => { ); }); - it('Shows the error when no appsync api exist', async () => { + it('Shows the error message, resolution & link to docs when no appsync api exist', async () => { ConfigOverrideManager.getInstance = jest.fn().mockReturnValue(jest.fn); const mockContext = { print: { + red: jest.fn(), + green: jest.fn(), error: jest.fn(), }, amplify: { @@ -82,12 +89,54 @@ describe('Test Mock API methods', () => { } as unknown as $TSContext; const testApi = new APITest(); - const testApiStartPromise = testApi.start(mockContext); + await testApi.start(mockContext); - await expect(testApiStartPromise).rejects.toThrow( - new AmplifyFault('MockProcessFault', { - message: 'Failed to start API Mocking.. Reason: No AppSync API is added to the project', + await expect(testApi['getAppSyncAPI'](mockContext)).rejects.toThrow( + new AmplifyError('MockProcessError', { + message: 'No AppSync API is added to the project', + resolution: `Use 'amplify add api' in the root of your app directory to create a GraphQL API.`, + link: 'https://docs.amplify.aws/cli/graphql/troubleshooting/', }), ); + expect(mockContext.print.green).toHaveBeenCalledWith( + '\n For troubleshooting the GraphQL API, visit https://docs.amplify.aws/cli/graphql/troubleshooting/ ', + ); + }); + + it('shows error message & resolution when amplify environment is not initialized', async () => { + ConfigOverrideManager.getInstance = jest.fn().mockReturnValue(jest.fn); + const mockContext = { + print: { + red: jest.fn(), + green: jest.fn(), + error: jest.fn(), + }, + parameters: { + options: { + help: false, + }, + }, + amplify: { + getEnvInfo: jest.fn(() => { + throw new AmplifyError('EnvironmentNotInitializedError', { + message: 'Current environment cannot be determined.', + resolution: `Use 'amplify init' in the root of your app directory to create a new environment.`, + }); + }), + loadRuntimePlugin: jest.fn().mockReturnValue({}), + addCleanUpTask: jest.fn, + pathManager: { + getAmplifyMetaFilePath: jest.fn(), + getGitIgnoreFilePath: jest.fn(), + }, + stateManager: { + localEnvInfoExists: false, + }, + readJsonFile: jest.fn().mockReturnValue({ api: {} }), + getProjectDetails: {}, + }, + } as unknown as $TSContext; + await run(mockContext); + await expect(mockContext.print.error).toHaveBeenCalledWith('Failed to start API Mocking.'); }); }); diff --git a/packages/amplify-util-mock/src/__tests__/api/lambda-arn-to-config.test.ts b/packages/amplify-util-mock/src/__tests__/api/lambda-arn-to-config.test.ts index 343c3d86d90..a192b97a8cf 100644 --- a/packages/amplify-util-mock/src/__tests__/api/lambda-arn-to-config.test.ts +++ b/packages/amplify-util-mock/src/__tests__/api/lambda-arn-to-config.test.ts @@ -1,9 +1,10 @@ -import { $TSContext } from '@aws-amplify/amplify-cli-core'; +import { $TSContext, AmplifyError } from '@aws-amplify/amplify-cli-core'; import { lambdaArnToConfig } from '../../api/lambda-arn-to-config'; import { ProcessedLambdaFunction } from '../../CFNParser/stack/types'; import { loadLambdaConfig } from '../../utils/lambda/load-lambda-config'; jest.mock('@aws-amplify/amplify-cli-core', () => ({ + ...(jest.requireActual('@aws-amplify/amplify-cli-core') as Record), pathManager: { getAmplifyPackageLibDirPath: jest.fn().mockReturnValue('test/path'), }, @@ -46,7 +47,6 @@ describe('lambda arn to config', () => { expect(loadLambdaConfig_mock.mock.calls[0][1]).toEqual('lambda1'); expect(result).toEqual(expectedLambdaConfig); }); - it('resolves Fn::Sub with params when lambda name is in template string', async () => { const result = await lambdaArnToConfig(context_stub, { 'Fn::Sub': [`some::arn::lambda2::{withsubs}::stuff`, { withsubs: 'a value' }] }); expect(loadLambdaConfig_mock.mock.calls[0][1]).toEqual('lambda2'); @@ -74,6 +74,12 @@ describe('lambda arn to config', () => { }); it('throws when arn is valid but no matching lambda found in the project', async () => { - expect(lambdaArnToConfig(context_stub, 'validformat::but::no::matchinglambda')).rejects.toThrowError(); + expect(lambdaArnToConfig(context_stub, 'validformat::but::no::matchinglambda')).rejects.toThrowError( + new AmplifyError('MockProcessError', { + message: `Did not find a Lambda matching ARN [\"validformat::but::no::matchinglambda\"] in the project. Local mocking only supports Lambdas that are configured in the project.`, + resolution: `Use 'amplify add function' in the root of your app directory to create a new Lambda Function. To connect an AWS Lambda resolver to the GraphQL API, add the @function directive to a field in your schema.`, + link: expect.any(String), + }), + ); }); }); diff --git a/packages/amplify-util-mock/src/api/api.ts b/packages/amplify-util-mock/src/api/api.ts index f761bf2663f..bd38e6b70a6 100644 --- a/packages/amplify-util-mock/src/api/api.ts +++ b/packages/amplify-util-mock/src/api/api.ts @@ -2,7 +2,7 @@ import * as fs from 'fs-extra'; import * as dynamoEmulator from 'amplify-dynamodb-simulator'; import { AmplifyAppSyncSimulator, AmplifyAppSyncSimulatorConfig } from '@aws-amplify/amplify-appsync-simulator'; import * as opensearchEmulator from '@aws-amplify/amplify-opensearch-simulator'; -import { $TSContext, $TSAny, AmplifyFault, AMPLIFY_SUPPORT_DOCS, isWindowsPlatform } from '@aws-amplify/amplify-cli-core'; +import { $TSContext, $TSAny, AmplifyFault, AMPLIFY_SUPPORT_DOCS, isWindowsPlatform, AmplifyError } from '@aws-amplify/amplify-cli-core'; import { add, generate, isCodegenConfigured, switchToSDLSchema } from 'amplify-codegen'; import * as path from 'path'; import * as chokidar from 'chokidar'; @@ -38,6 +38,7 @@ export const GRAPHQL_API_ENDPOINT_OUTPUT = 'GraphQLAPIEndpointOutput'; export const GRAPHQL_API_KEY_OUTPUT = 'GraphQLAPIKeyOutput'; export const MOCK_API_KEY = 'da2-fakeApiId123456'; export const MOCK_API_PORT = 20002; +const errorSuffix = `\n For troubleshooting the GraphQL API, visit ${AMPLIFY_SUPPORT_DOCS.CLI_GRAPHQL_TROUBLESHOOTING.url} `; export class APITest { private apiName: string; @@ -92,10 +93,12 @@ export class APITest { const errMessage = 'Failed to start API Mocking.'; context.print.error(errMessage + ' Running cleanup tasks.'); await this.stop(context); - throw new AmplifyFault('MockProcessFault', { - message: `${errMessage}. Reason: ${e?.message}`, - link: AMPLIFY_SUPPORT_DOCS.CLI_GRAPHQL_TROUBLESHOOTING.url, - }); + if (e.resolution == undefined || e.link == undefined) { + context.print.red(`Reason: ${e.message}`); + } else { + context.print.red(`Reason: ${e.message}\nResolution: ${e.resolution}`); + context.print.green(`${e.link}`); + } } } @@ -369,7 +372,7 @@ export class APITest { const ddbConfig = this.ddbClient.config; return configureDDBDataSource(config, ddbConfig); } - private async getAppSyncAPI(context) { + public async getAppSyncAPI(context) { const currentMeta = await getAmplifyMeta(context); const { api: apis = {} } = currentMeta; let name = null; @@ -381,9 +384,10 @@ export class APITest { return undefined; }); if (!name) { - throw new AmplifyFault('MockProcessFault', { + throw new AmplifyError('MockProcessError', { message: 'No AppSync API is added to the project', - link: AMPLIFY_SUPPORT_DOCS.CLI_GRAPHQL_TROUBLESHOOTING.url, + resolution: `Use 'amplify add api' in the root of your app directory to create a GraphQL API.`, + link: `${errorSuffix}`, }); } return name; diff --git a/packages/amplify-util-mock/src/api/lambda-arn-to-config.ts b/packages/amplify-util-mock/src/api/lambda-arn-to-config.ts index 400089e01a6..50871068c55 100644 --- a/packages/amplify-util-mock/src/api/lambda-arn-to-config.ts +++ b/packages/amplify-util-mock/src/api/lambda-arn-to-config.ts @@ -1,10 +1,16 @@ import { keys } from 'lodash'; -import { $TSAny, $TSContext, stateManager, ApiCategoryFacade, getGraphQLTransformerFunctionDocLink } from '@aws-amplify/amplify-cli-core'; +import { + $TSAny, + $TSContext, + stateManager, + ApiCategoryFacade, + getGraphQLTransformerFunctionDocLink, + AmplifyError, +} from '@aws-amplify/amplify-cli-core'; import _ = require('lodash'); import { ServiceName } from '@aws-amplify/amplify-category-function'; import { loadLambdaConfig } from '../utils/lambda/load-lambda-config'; import { ProcessedLambdaFunction } from '../CFNParser/stack/types'; - /** * Attempts to match an arn object against the array of lambdas configured in the project */ @@ -32,11 +38,13 @@ export const lambdaArnToConfig = async (context: $TSContext, arn: $TSAny): Promi .map(([key]) => key); const foundLambdaName = lambdaNames.find((name) => searchString.includes(name)); if (!foundLambdaName) { - throw new Error( - `Did not find a Lambda matching ARN [${JSON.stringify( + throw new AmplifyError('MockProcessError', { + message: `Did not find a Lambda matching ARN [${JSON.stringify( arn, - )}] in the project. Local mocking only supports Lambdas that are configured in the project.${errorSuffix}`, - ); + )}] in the project. Local mocking only supports Lambdas that are configured in the project.`, + resolution: `Use 'amplify add function' in the root of your app directory to create a new Lambda Function. To connect an AWS Lambda resolver to the GraphQL API, add the @function directive to a field in your schema.`, + link: `${errorSuffix}`, + }); } // lambdaArnToConfig is only called in the context of initializing a mock API, so setting overrideApiToLocal to true here return loadLambdaConfig(context, foundLambdaName, true); diff --git a/packages/amplify-util-mock/src/commands/mock/api.ts b/packages/amplify-util-mock/src/commands/mock/api.ts index 612524e33b4..fe45c3b8125 100644 --- a/packages/amplify-util-mock/src/commands/mock/api.ts +++ b/packages/amplify-util-mock/src/commands/mock/api.ts @@ -1,6 +1,5 @@ import { $TSContext } from '@aws-amplify/amplify-cli-core'; import { start } from '../../api'; - export const name = 'api'; export const run = async (context: $TSContext) => { @@ -11,8 +10,16 @@ export const run = async (context: $TSContext) => { return; } try { + // added here to get the Env info before starting to mock + await context.amplify.getEnvInfo(); await start(context); } catch (e) { - context.print.error(e.message); + context.print.error(`Failed to start API Mocking.`); + if (e.resolution == undefined || e.link == undefined) { + context.print.red(`Reason: ${e.message}`); + } else { + context.print.red(`Reason: ${e.message}\nResolution: ${e.resolution}`); + context.print.green(`For troubleshooting guide, visit: ${e.link}`); + } } };