Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add error mapping for Amplify app not found in specified region #2313

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/ninety-coins-jog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@aws-amplify/backend-cli': patch
---

Added error mapping for app name not available in the region
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ import { BackendIdentifierResolverWithFallback } from '../../../backend-identifi
import { S3Client } from '@aws-sdk/client-s3';
import { AmplifyClient } from '@aws-sdk/client-amplify';
import { CloudFormationClient } from '@aws-sdk/client-cloudformation';
import {
BackendOutputClientError,
BackendOutputClientErrorType,
} from '@aws-amplify/deployed-backend-client';
import { AmplifyUserError } from '@aws-amplify/platform-core';

void describe('generate graphql-client-code command', () => {
const generateApiCodeAdapter = new GenerateApiCodeAdapter({
Expand Down Expand Up @@ -356,3 +361,149 @@ void describe('generate graphql-client-code command', () => {
assert.match(output, /Arguments .* are mutually exclusive/);
});
});

void describe('GenerateGraphqlClientCodeCommand error handling', () => {
let generateApiCodeAdapter: GenerateApiCodeAdapter;
let backendIdentifierResolver: AppBackendIdentifierResolver;
let generateGraphqlClientCodeCommand: GenerateGraphqlClientCodeCommand;

beforeEach(() => {
// Mock the dependencies
generateApiCodeAdapter = {
invokeGenerateApiCode: mock.fn(),
} as unknown as GenerateApiCodeAdapter;

backendIdentifierResolver = {
resolveDeployedBackendIdentifier: mock.fn(),
} as unknown as AppBackendIdentifierResolver;

generateGraphqlClientCodeCommand = new GenerateGraphqlClientCodeCommand(
generateApiCodeAdapter,
backendIdentifierResolver
);
});

void it('should throw AmplifyUserError when NO_APP_FOUND_ERROR occurs', async () => {
// Mock the resolver to simulate successful resolution
mock.method(
backendIdentifierResolver,
'resolveDeployedBackendIdentifier',
() => Promise.resolve({ appId: 'test-app', branchName: 'main' })
);

// Mock the adapter to throw NO_APP_FOUND_ERROR
mock.method(generateApiCodeAdapter, 'invokeGenerateApiCode', () => {
throw new BackendOutputClientError(
BackendOutputClientErrorType.NO_APP_FOUND_ERROR,
'No Amplify app found in the specified region'
);
});

try {
await generateGraphqlClientCodeCommand.handler({
stack: undefined,
appId: 'test-app',
'app-id': 'test-app',
branch: 'main',
format: undefined,
modelTarget: undefined,
'model-target': undefined,
statementTarget: undefined,
'statement-target': undefined,
typeTarget: undefined,
'type-target': undefined,
out: undefined,
modelGenerateIndexRules: undefined,
'model-generate-index-rules': undefined,
modelEmitAuthProvider: undefined,
'model-emit-auth-provider': undefined,
modelRespectPrimaryKeyAttributesOnConnectionField: undefined,
'model-respect-primary-key-attributes-on-connection-field': undefined,
modelGenerateModelsForLazyLoadAndCustomSelectionSet: undefined,
'model-generate-models-for-lazy-load-and-custom-selection-set':
undefined,
modelAddTimestampFields: undefined,
'model-add-timestamp-fields': undefined,
modelHandleListNullabilityTransparently: undefined,
'model-handle-list-nullability-transparently': undefined,
statementMaxDepth: undefined,
'statement-max-depth': undefined,
statementTypenameIntrospection: undefined,
'statement-typename-introspection': undefined,
typeMultipleSwiftFiles: undefined,
'type-multiple-swift-files': undefined,
_: [],
$0: 'command-name',
});
assert.fail('Expected error was not thrown');
} catch (error) {
if (error instanceof AmplifyUserError) {
assert.equal(error.name, 'AmplifyAppNotFoundError');
assert.equal(
error.message,
'No Amplify app found in the specified region'
);
assert.equal(
error.resolution,
'Ensure that an Amplify app exists in the region.'
);
}
}
});

void it('should re-throw other types of errors', async () => {
// Mock the resolver to simulate successful resolution
mock.method(
backendIdentifierResolver,
'resolveDeployedBackendIdentifier',
() => Promise.resolve({ appId: 'test-app', branchName: 'main' })
);

// Mock the adapter to throw a different type of error
const originalError = new Error('Some other error');
mock.method(generateApiCodeAdapter, 'invokeGenerateApiCode', () => {
throw originalError;
});

try {
await generateGraphqlClientCodeCommand.handler({
stack: undefined,
appId: 'test-app',
'app-id': 'test-app',
branch: 'main',
format: undefined,
modelTarget: undefined,
'model-target': undefined,
statementTarget: undefined,
'statement-target': undefined,
typeTarget: undefined,
'type-target': undefined,
out: undefined,
modelGenerateIndexRules: undefined,
'model-generate-index-rules': undefined,
modelEmitAuthProvider: undefined,
'model-emit-auth-provider': undefined,
modelRespectPrimaryKeyAttributesOnConnectionField: undefined,
'model-respect-primary-key-attributes-on-connection-field': undefined,
modelGenerateModelsForLazyLoadAndCustomSelectionSet: undefined,
'model-generate-models-for-lazy-load-and-custom-selection-set':
undefined,
modelAddTimestampFields: undefined,
'model-add-timestamp-fields': undefined,
modelHandleListNullabilityTransparently: undefined,
'model-handle-list-nullability-transparently': undefined,
statementMaxDepth: undefined,
'statement-max-depth': undefined,
statementTypenameIntrospection: undefined,
'statement-typename-introspection': undefined,
typeMultipleSwiftFiles: undefined,
'type-multiple-swift-files': undefined,
_: [],
$0: 'command-name',
});
assert.fail('Expected error was not thrown');
} catch (error) {
assert.equal(error, originalError);
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
GenerateModelsOptions,
} from '@aws-amplify/model-generator';
import { ArgumentsKebabCase } from '../../../kebab_case.js';
import { AmplifyUserError } from '@aws-amplify/platform-core';
import {
BackendOutputClientError,
BackendOutputClientErrorType,
} from '@aws-amplify/deployed-backend-client';

type GenerateOptions =
| GenerateGraphqlCodegenOptions
Expand Down Expand Up @@ -89,20 +94,38 @@
handler = async (
args: ArgumentsCamelCase<GenerateGraphqlClientCodeCommandOptions>
): Promise<void> => {
const backendIdentifier =
await this.backendIdentifierResolver.resolveDeployedBackendIdentifier(
args
);
const out = this.getOutDir(args);
const format = args.format ?? GenerateApiCodeFormat.GRAPHQL_CODEGEN;
const formatParams = this.formatParamBuilders[format](args);
try {
const backendIdentifier =
await this.backendIdentifierResolver.resolveDeployedBackendIdentifier(
args
);
const out = this.getOutDir(args);
const format = args.format ?? GenerateApiCodeFormat.GRAPHQL_CODEGEN;
const formatParams = this.formatParamBuilders[format](args);

const result = await this.generateApiCodeAdapter.invokeGenerateApiCode({
...backendIdentifier,
...formatParams,
} as unknown as InvokeGenerateApiCodeProps);
const result = await this.generateApiCodeAdapter.invokeGenerateApiCode({
...backendIdentifier,
...formatParams,
} as unknown as InvokeGenerateApiCodeProps);

await result.writeToDirectory(out);
await result.writeToDirectory(out);
} catch (error) {
if (
error instanceof BackendOutputClientError &&

Check failure on line 114 in packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts

View workflow job for this annotation

GitHub Actions / lint

Do not use instanceof with BackendOutputClientError. Use BackendOutputClientError.isBackendOutputClientError instead
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per the lint, don't use instanceof as it might not work if there are multiple versions of DeployedBackendClient package in the node_modules.

error.code === BackendOutputClientErrorType.NO_APP_FOUND_ERROR
) {
throw new AmplifyUserError(
'AmplifyAppNotFoundError',
{
message: error.message,
resolution: `Ensure that an Amplify app exists in the region.`,
},
error
);
}
// Re-throw any other errors
throw error;
}
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import { SandboxBackendIdResolver } from '../../sandbox/sandbox_id_resolver.js';
import { S3Client } from '@aws-sdk/client-s3';
import { AmplifyClient } from '@aws-sdk/client-amplify';
import { CloudFormationClient } from '@aws-sdk/client-cloudformation';
import {
BackendOutputClientError,
BackendOutputClientErrorType,
} from '@aws-amplify/deployed-backend-client';
import { AmplifyUserError } from '@aws-amplify/platform-core';

void describe('generate outputs command', () => {
const clientConfigGeneratorAdapter = new ClientConfigGeneratorAdapter({
Expand Down Expand Up @@ -249,3 +254,101 @@ void describe('generate outputs command', () => {
assert.match(output, /Arguments .* mutually exclusive/);
});
});

void describe('GenerateOutputsCommand error handling', () => {
let clientConfigGenerator: ClientConfigGeneratorAdapter;
let backendIdentifierResolver: AppBackendIdentifierResolver;
let generateOutputsCommand: GenerateOutputsCommand;

beforeEach(() => {
// Mock the dependencies
clientConfigGenerator = {
generateClientConfigToFile: mock.fn(),
} as unknown as ClientConfigGeneratorAdapter;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use the existing describe that is already doing all the mock setup

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated it, keeping it consistent
I was planning to create Test suites separate for handling all negative test cases

backendIdentifierResolver = {
resolveDeployedBackendIdentifier: mock.fn(),
} as unknown as AppBackendIdentifierResolver;

generateOutputsCommand = new GenerateOutputsCommand(
clientConfigGenerator,
backendIdentifierResolver
);
});

void it('should throw AmplifyUserError when NO_APP_FOUND_ERROR occurs', async () => {
// Mock the resolver to simulate successful resolution
mock.method(
backendIdentifierResolver,
'resolveDeployedBackendIdentifier',
() => Promise.resolve({ appId: 'test-app', branchName: 'main' })
);

// Mock the generator to throw NO_APP_FOUND_ERROR
mock.method(clientConfigGenerator, 'generateClientConfigToFile', () => {
throw new BackendOutputClientError(
BackendOutputClientErrorType.NO_APP_FOUND_ERROR,
'No Amplify app found in the specified region'
);
});
try {
await generateOutputsCommand.handler({
stack: undefined,
appId: 'test-app',
'app-id': 'test-app',
branch: 'main',
format: undefined,
outDir: undefined,
'out-dir': undefined,
outputsVersion: '1.3',
'outputs-version': '1.3',
_: [],
$0: 'command-name',
});
assert.fail('Expected error was not thrown');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use commandRunner.runCommand as used in other tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated it

} catch (error) {
if (error instanceof AmplifyUserError) {
assert.equal(error.name, 'AmplifyAppNotFoundError');
assert.equal(
error.message,
'No Amplify app found in the specified region'
);
}
}
});

void it('should re-throw other types of errors', async () => {
// Mock the resolver to simulate successful resolution
mock.method(
backendIdentifierResolver,
'resolveDeployedBackendIdentifier',
() => Promise.resolve({ appId: 'test-app', branchName: 'main' })
);

// Mock the generator to throw a different type of error
const originalError = new Error('Some other error');
mock.method(clientConfigGenerator, 'generateClientConfigToFile', () => {
throw originalError;
});

try {
await generateOutputsCommand.handler({
stack: undefined,
appId: 'test-app',
'app-id': 'test-app',
branch: 'main',
format: undefined,
outDir: undefined,
'out-dir': undefined,
outputsVersion: '1.3',
'outputs-version': '1.3',
_: [],
$0: 'command-name',
});
assert.fail('Expected error was not thrown');
} catch (error) {
assert.equal(error, originalError);
assert(!(error instanceof AmplifyUserError));
}
});
});
Loading
Loading