From 0ed0e84bba2394af9a0963731614c21b823e5021 Mon Sep 17 00:00:00 2001 From: Aaron S Date: Wed, 13 Nov 2024 12:29:39 -0600 Subject: [PATCH 1/4] test: Add e2e test for data client --- amplify_outputs.json | 3 + package-lock.json | 2 +- .../data_and_function_project.sandbox.test.ts | 4 + .../data_and_function_project.ts | 176 ++++++++++++++++++ .../data-and-function/amplify/backend.ts | 5 + .../amplify/data/resource.ts | 32 ++++ .../amplify/functions/todo-count/handler.ts | 17 ++ .../amplify/functions/todo-count/resource.ts | 7 + 8 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 amplify_outputs.json create mode 100644 packages/integration-tests/src/test-e2e/sandbox/data_and_function_project.sandbox.test.ts create mode 100644 packages/integration-tests/src/test-project-setup/data_and_function_project.ts create mode 100644 packages/integration-tests/src/test-projects/data-and-function/amplify/backend.ts create mode 100644 packages/integration-tests/src/test-projects/data-and-function/amplify/data/resource.ts create mode 100644 packages/integration-tests/src/test-projects/data-and-function/amplify/functions/todo-count/handler.ts create mode 100644 packages/integration-tests/src/test-projects/data-and-function/amplify/functions/todo-count/resource.ts diff --git a/amplify_outputs.json b/amplify_outputs.json new file mode 100644 index 00000000000..efc4cbd5b56 --- /dev/null +++ b/amplify_outputs.json @@ -0,0 +1,3 @@ +{ + "version": "1.3" +} diff --git a/package-lock.json b/package-lock.json index 6110d2f2b4e..3482fbc2eaa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31931,7 +31931,7 @@ "@aws-amplify/backend-output-storage": "^1.1.3", "@aws-amplify/data-construct": "^1.10.1", "@aws-amplify/data-schema-types": "^1.2.0", - "@aws-amplify/graphql-generator": "^0.5.0", + "@aws-amplify/graphql-generator": "^0.5.1", "@aws-amplify/plugin-types": "^1.4.0" }, "devDependencies": { diff --git a/packages/integration-tests/src/test-e2e/sandbox/data_and_function_project.sandbox.test.ts b/packages/integration-tests/src/test-e2e/sandbox/data_and_function_project.sandbox.test.ts new file mode 100644 index 00000000000..b37217f777d --- /dev/null +++ b/packages/integration-tests/src/test-e2e/sandbox/data_and_function_project.sandbox.test.ts @@ -0,0 +1,4 @@ +import { defineSandboxTest } from './sandbox.test.template.js'; +import { DataAndFunctionTestProjectCreator } from '../../test-project-setup/data_and_function_project.js'; + +defineSandboxTest(new DataAndFunctionTestProjectCreator()); diff --git a/packages/integration-tests/src/test-project-setup/data_and_function_project.ts b/packages/integration-tests/src/test-project-setup/data_and_function_project.ts new file mode 100644 index 00000000000..8806b6d2a6f --- /dev/null +++ b/packages/integration-tests/src/test-project-setup/data_and_function_project.ts @@ -0,0 +1,176 @@ +import { TestProjectBase } from './test_project_base.js'; +import fs from 'fs/promises'; +import { createEmptyAmplifyProject } from './create_empty_amplify_project.js'; +import { CloudFormationClient } from '@aws-sdk/client-cloudformation'; +import { TestProjectCreator } from './test_project_creator.js'; +import { AmplifyClient } from '@aws-sdk/client-amplify'; +import { BackendIdentifier } from '@aws-amplify/plugin-types'; +import { LambdaClient } from '@aws-sdk/client-lambda'; +import { DeployedResourcesFinder } from '../find_deployed_resource.js'; +import { generateClientConfig } from '@aws-amplify/client-config'; +import { CognitoIdentityProviderClient } from '@aws-sdk/client-cognito-identity-provider'; +import { SemVer } from 'semver'; +import crypto from 'node:crypto'; +import { + ApolloClient, + ApolloLink, + HttpLink, + InMemoryCache, +} from '@apollo/client/core'; +import { AUTH_TYPE, createAuthLink } from 'aws-appsync-auth-link'; +import { gql } from 'graphql-tag'; +import assert from 'assert'; +import { NormalizedCacheObject } from '@apollo/client'; +import { e2eToolingClientConfig } from '../e2e_tooling_client_config.js'; + +// TODO: this is a work around +// it seems like as of amplify v6 , some of the code only runs in the browser ... +// see https://github.com/aws-amplify/amplify-js/issues/12751 +if (process.versions.node) { + // node >= 20 now exposes crypto by default. This workaround is not needed: https://github.com/nodejs/node/pull/42083 + if (new SemVer(process.versions.node).major < 20) { + // @ts-expect-error altering typing for global to make compiler happy is not worth the effort assuming this is temporary workaround + globalThis.crypto = crypto; + } +} + +/** + * Creates the data and function test project. + */ +export class DataAndFunctionTestProjectCreator implements TestProjectCreator { + readonly name = 'data-and-function'; + + /** + * Creates project creator. + */ + constructor( + private readonly cfnClient: CloudFormationClient = new CloudFormationClient( + e2eToolingClientConfig + ), + private readonly amplifyClient: AmplifyClient = new AmplifyClient( + e2eToolingClientConfig + ), + private readonly lambdaClient: LambdaClient = new LambdaClient( + e2eToolingClientConfig + ), + private readonly cognitoIdentityProviderClient: CognitoIdentityProviderClient = new CognitoIdentityProviderClient( + e2eToolingClientConfig + ), + private readonly resourceFinder: DeployedResourcesFinder = new DeployedResourcesFinder() + ) {} + + createProject = async (e2eProjectDir: string): Promise => { + const { projectName, projectRoot, projectAmplifyDir } = + await createEmptyAmplifyProject(this.name, e2eProjectDir); + + const project = new DataAndFunctionTestProject( + projectName, + projectRoot, + projectAmplifyDir, + this.cfnClient, + this.amplifyClient, + this.lambdaClient, + this.cognitoIdentityProviderClient, + this.resourceFinder + ); + await fs.cp( + project.sourceProjectAmplifyDirURL, + project.projectAmplifyDirPath, + { + recursive: true, + } + ); + return project; + }; +} + +/** + * The data and function test project. + */ +class DataAndFunctionTestProject extends TestProjectBase { + readonly sourceProjectDirPath = '../../src/test-projects/data-and-function'; + + readonly sourceProjectAmplifyDirSuffix = `${this.sourceProjectDirPath}/amplify`; + + readonly sourceProjectAmplifyDirURL: URL = new URL( + this.sourceProjectAmplifyDirSuffix, + import.meta.url + ); + + /** + * Create a test project instance. + */ + constructor( + name: string, + projectDirPath: string, + projectAmplifyDirPath: string, + cfnClient: CloudFormationClient, + amplifyClient: AmplifyClient, + private readonly lambdaClient: LambdaClient = new LambdaClient( + e2eToolingClientConfig + ), + private readonly cognitoIdentityProviderClient: CognitoIdentityProviderClient = new CognitoIdentityProviderClient( + e2eToolingClientConfig + ), + private readonly resourceFinder: DeployedResourcesFinder = new DeployedResourcesFinder() + ) { + super( + name, + projectDirPath, + projectAmplifyDirPath, + cfnClient, + amplifyClient + ); + } + + override async assertPostDeployment( + backendId: BackendIdentifier + ): Promise { + await super.assertPostDeployment(backendId); + + const clientConfig = await generateClientConfig(backendId, '1.1'); + if (!clientConfig.data?.url) { + throw new Error('Data and function project must include data'); + } + if (!clientConfig.data.api_key) { + throw new Error('Data and function project must include api_key'); + } + + // const dataUrl = clientConfig.data?.url; + + const httpLink = new HttpLink({ uri: clientConfig.data.url }); + const link = ApolloLink.from([ + createAuthLink({ + url: clientConfig.data.url, + region: clientConfig.data.aws_region, + auth: { + type: AUTH_TYPE.API_KEY, + apiKey: clientConfig.data.api_key, + }, + }), + // see https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/473#issuecomment-543029072 + httpLink, + ]); + const apolloClient = new ApolloClient({ + link, + cache: new InMemoryCache(), + }); + + await this.assertDataFunctionCallSucceeds(apolloClient); + } + + private assertDataFunctionCallSucceeds = async ( + apolloClient: ApolloClient + ): Promise => { + const response = await apolloClient.query({ + query: gql` + query todoCount { + todoCount + } + `, + variables: {}, + }); + + assert.equal(response.data, 0); + }; +} diff --git a/packages/integration-tests/src/test-projects/data-and-function/amplify/backend.ts b/packages/integration-tests/src/test-projects/data-and-function/amplify/backend.ts new file mode 100644 index 00000000000..f909735c8ac --- /dev/null +++ b/packages/integration-tests/src/test-projects/data-and-function/amplify/backend.ts @@ -0,0 +1,5 @@ +import { defineBackend } from '@aws-amplify/backend'; +import { data } from './data/resource.js'; +import { todoCount } from './functions/todo-count/resource.js'; + +const backend = defineBackend({ data, todoCount }); diff --git a/packages/integration-tests/src/test-projects/data-and-function/amplify/data/resource.ts b/packages/integration-tests/src/test-projects/data-and-function/amplify/data/resource.ts new file mode 100644 index 00000000000..8785c9a6b65 --- /dev/null +++ b/packages/integration-tests/src/test-projects/data-and-function/amplify/data/resource.ts @@ -0,0 +1,32 @@ +import { a, ClientSchema, defineData } from '@aws-amplify/backend'; +import { todoCount } from '../functions/todo-count/resource.js'; + +const schema = a + .schema({ + Todo: a + .model({ + title: a.string().required(), + done: a.boolean().default(false), // default value is false + }) + .authorization((allow) => [allow.publicApiKey()]), + todoCount: a + .query() + .arguments({}) + .returns(a.integer()) + .handler(a.handler.function(todoCount)) + .authorization((allow) => [allow.publicApiKey()]), + }) + .authorization((allow) => [allow.resource(todoCount)]); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'apiKey', + // API Key is used for a.allow.public() rules + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); diff --git a/packages/integration-tests/src/test-projects/data-and-function/amplify/functions/todo-count/handler.ts b/packages/integration-tests/src/test-projects/data-and-function/amplify/functions/todo-count/handler.ts new file mode 100644 index 00000000000..743ec3c0015 --- /dev/null +++ b/packages/integration-tests/src/test-projects/data-and-function/amplify/functions/todo-count/handler.ts @@ -0,0 +1,17 @@ +import type { Handler } from 'aws-lambda'; +import { Amplify } from 'aws-amplify'; +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../../data/resource.js'; +import { + resourceConfig, + libraryOptions, +} from '$amplify/data-config/todo-count'; + +Amplify.configure(resourceConfig, libraryOptions); + +const client = generateClient(); + +export const handler: Handler = async () => { + const todos = await client.models.Todo.list(); + return todos.data.length; +}; diff --git a/packages/integration-tests/src/test-projects/data-and-function/amplify/functions/todo-count/resource.ts b/packages/integration-tests/src/test-projects/data-and-function/amplify/functions/todo-count/resource.ts new file mode 100644 index 00000000000..6fa45f4b81f --- /dev/null +++ b/packages/integration-tests/src/test-projects/data-and-function/amplify/functions/todo-count/resource.ts @@ -0,0 +1,7 @@ +import { defineFunction } from '@aws-amplify/backend'; + +export const todoCount = defineFunction({ + name: 'todo-count', + entry: './handler.ts', + timeoutSeconds: 30, +}); From 65dfd180fadc13de6709f7674a3ca4d1598836f0 Mon Sep 17 00:00:00 2001 From: Aaron S Date: Wed, 13 Nov 2024 15:58:37 -0600 Subject: [PATCH 2/4] fix: integ test and tune user error --- package-lock.json | 8 ++++---- packages/backend-function/package.json | 2 +- .../runtime/get_amplify_clients_configuration.ts | 13 ++++++++++--- packages/integration-tests/tsconfig.json | 7 ++++++- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 978917d1e96..fa624aee48e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2838,9 +2838,9 @@ } }, "node_modules/@aws-amplify/data-schema": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@aws-amplify/data-schema/-/data-schema-1.5.1.tgz", - "integrity": "sha512-hFDqqwHqdoFazmvGOApCX8kqrdoum9YJikmAQN5tP2sgnCT++lqznFw2F4PPqDJRxhQP1AYuwhbbRBvGLMbs/w==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@aws-amplify/data-schema/-/data-schema-1.13.4.tgz", + "integrity": "sha512-vtwcu8SSg2iGaDF/uJ6eGf8kiZSxFWmDhYJ5bJ4vkv91y4na1nZCphv40ehyRdAiMKVKmdLuBhVmDxy5g1s8eA==", "license": "Apache-2.0", "dependencies": { "@aws-amplify/data-schema-types": "*", @@ -31967,12 +31967,12 @@ "@aws-amplify/backend-output-schemas": "^1.4.0", "@aws-amplify/backend-output-storage": "^1.1.3", "@aws-amplify/plugin-types": "^1.4.0", + "@aws-sdk/client-s3": "^3.624.0", "execa": "^8.0.1" }, "devDependencies": { "@aws-amplify/backend-platform-test-stubs": "^0.3.6", "@aws-amplify/platform-core": "^1.1.0", - "@aws-sdk/client-s3": "^3.624.0", "@aws-sdk/client-ssm": "^3.624.0", "aws-sdk": "^2.1550.0", "uuid": "^9.0.1" diff --git a/packages/backend-function/package.json b/packages/backend-function/package.json index 17d8d327934..f3d74edc01d 100644 --- a/packages/backend-function/package.json +++ b/packages/backend-function/package.json @@ -27,12 +27,12 @@ "@aws-amplify/backend-output-schemas": "^1.4.0", "@aws-amplify/backend-output-storage": "^1.1.3", "@aws-amplify/plugin-types": "^1.4.0", + "@aws-sdk/client-s3": "^3.624.0", "execa": "^8.0.1" }, "devDependencies": { "@aws-amplify/backend-platform-test-stubs": "^0.3.6", "@aws-amplify/platform-core": "^1.1.0", - "@aws-sdk/client-s3": "^3.624.0", "@aws-sdk/client-ssm": "^3.624.0", "aws-sdk": "^2.1550.0", "uuid": "^9.0.1" diff --git a/packages/backend-function/src/runtime/get_amplify_clients_configuration.ts b/packages/backend-function/src/runtime/get_amplify_clients_configuration.ts index b665f57532f..a3201443220 100644 --- a/packages/backend-function/src/runtime/get_amplify_clients_configuration.ts +++ b/packages/backend-function/src/runtime/get_amplify_clients_configuration.ts @@ -49,7 +49,8 @@ const getResourceConfig = ( endpoint: env.AMPLIFY_DATA_GRAPHQL_ENDPOINT, region: env.AWS_REGION, defaultAuthMode: 'iam' as const, - modelIntrospection: modelIntrospectionSchema, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + modelIntrospection: modelIntrospectionSchema as any, }, }, }; @@ -74,10 +75,15 @@ const getLibraryOptions = (env: DataClientEnv) => { }; }; +type InvalidConfig = unknown & { + invalidType: 'This function needs to be granted `authorization((allow) => [allow.resource(fcn)])` on the data schema.'; +}; + type DataClientError = { - resourceConfig: Record; - libraryOptions: Record; + resourceConfig: InvalidConfig; + libraryOptions: InvalidConfig; }; + type DataClientConfig = { resourceConfig: ReturnType; libraryOptions: ReturnType; @@ -114,6 +120,7 @@ export const getAmplifyClientsConfigurationRetriever = async ( s3Client: S3Client ): Promise> => { if (!isDataClientEnv(env)) { + // TEST return { resourceConfig: {}, libraryOptions: {} } as DataClientReturn; } let modelIntrospectionSchema: object; diff --git a/packages/integration-tests/tsconfig.json b/packages/integration-tests/tsconfig.json index 4f472fc34a6..e11568123ee 100644 --- a/packages/integration-tests/tsconfig.json +++ b/packages/integration-tests/tsconfig.json @@ -13,5 +13,10 @@ { "path": "../plugin-types" }, { "path": "./src/test-projects/data-storage-auth-with-triggers-ts" } ], - "exclude": ["**/node_modules", "**/lib", "src/e2e-tests"] + "exclude": [ + "**/node_modules", + "**/lib", + "src/e2e-tests", + "**/amplify/**/handler.ts" + ] } From fa39c8c672b706d11a8e022871ec9b3ae21d74ad Mon Sep 17 00:00:00 2001 From: Aaron S Date: Wed, 13 Nov 2024 16:21:15 -0600 Subject: [PATCH 3/4] Fix test assert --- .../deployment/data_and_function_project.deployment.test.ts | 4 ++++ .../src/test-project-setup/data_and_function_project.ts | 2 +- .../data-and-function/amplify/functions/todo-count/handler.ts | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 packages/integration-tests/src/test-e2e/deployment/data_and_function_project.deployment.test.ts diff --git a/packages/integration-tests/src/test-e2e/deployment/data_and_function_project.deployment.test.ts b/packages/integration-tests/src/test-e2e/deployment/data_and_function_project.deployment.test.ts new file mode 100644 index 00000000000..ba756dc6d22 --- /dev/null +++ b/packages/integration-tests/src/test-e2e/deployment/data_and_function_project.deployment.test.ts @@ -0,0 +1,4 @@ +import { defineDeploymentTest } from './deployment.test.template.js'; +import { DataAndFunctionTestProjectCreator } from '../../test-project-setup/data_and_function_project.js'; + +defineDeploymentTest(new DataAndFunctionTestProjectCreator()); diff --git a/packages/integration-tests/src/test-project-setup/data_and_function_project.ts b/packages/integration-tests/src/test-project-setup/data_and_function_project.ts index 8806b6d2a6f..9a2cad05fb8 100644 --- a/packages/integration-tests/src/test-project-setup/data_and_function_project.ts +++ b/packages/integration-tests/src/test-project-setup/data_and_function_project.ts @@ -171,6 +171,6 @@ class DataAndFunctionTestProject extends TestProjectBase { variables: {}, }); - assert.equal(response.data, 0); + assert.deepEqual(response.data, { todoCount: 0 }); }; } diff --git a/packages/integration-tests/src/test-projects/data-and-function/amplify/functions/todo-count/handler.ts b/packages/integration-tests/src/test-projects/data-and-function/amplify/functions/todo-count/handler.ts index 743ec3c0015..0c0b7af2cd4 100644 --- a/packages/integration-tests/src/test-projects/data-and-function/amplify/functions/todo-count/handler.ts +++ b/packages/integration-tests/src/test-projects/data-and-function/amplify/functions/todo-count/handler.ts @@ -2,6 +2,7 @@ import type { Handler } from 'aws-lambda'; import { Amplify } from 'aws-amplify'; import { generateClient } from 'aws-amplify/data'; import type { Schema } from '../../data/resource.js'; +// @ts-ignore import { resourceConfig, libraryOptions, From efc059c547f7a4ed7f1b503646b5e3975032b51c Mon Sep 17 00:00:00 2001 From: Aaron S Date: Wed, 13 Nov 2024 16:38:25 -0600 Subject: [PATCH 4/4] Remove amplify outputs --- amplify_outputs.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 amplify_outputs.json diff --git a/amplify_outputs.json b/amplify_outputs.json deleted file mode 100644 index efc4cbd5b56..00000000000 --- a/amplify_outputs.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "version": "1.3" -}