Skip to content
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion packages/backend-function/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
"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"
Expand Down
Original file line number Diff line number Diff line change
@@ -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());
Original file line number Diff line number Diff line change
@@ -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());
Original file line number Diff line number Diff line change
@@ -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<TestProjectBase> => {
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<void> {
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<NormalizedCacheObject>
): Promise<void> => {
const response = await apolloClient.query<number>({
query: gql`
query todoCount {
todoCount
}
`,
variables: {},
});

assert.deepEqual(response.data, { todoCount: 0 });
};
}
Original file line number Diff line number Diff line change
@@ -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 });
Original file line number Diff line number Diff line change
@@ -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<typeof schema>;

export const data = defineData({
schema,
authorizationModes: {
defaultAuthorizationMode: 'apiKey',
// API Key is used for a.allow.public() rules
apiKeyAuthorizationMode: {
expiresInDays: 30,
},
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Esbuild during synth is fine with all of this, but the ts check before synth is unhappy. It looks like a workaround was used for data-storage-auth-with-triggers-ts of setting up fake files to make the pre-synth check happy. Should I replicate that here? It would avoid the exclude change in the tsconfig below too.

Seems fine, but also very fake since this file isn't being built in the project.

import {
resourceConfig,
libraryOptions,
} from '$amplify/data-config/todo-count';

Amplify.configure(resourceConfig, libraryOptions);

const client = generateClient<Schema>();

export const handler: Handler = async () => {
const todos = await client.models.Todo.list();
return todos.data.length;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineFunction } from '@aws-amplify/backend';

export const todoCount = defineFunction({
name: 'todo-count',
entry: './handler.ts',
timeoutSeconds: 30,
});
7 changes: 6 additions & 1 deletion packages/integration-tests/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}