Skip to content

Commit c2fa037

Browse files
committed
test: Add e2e test for data client
1 parent 9c07c76 commit c2fa037

File tree

10 files changed

+307
-3
lines changed

10 files changed

+307
-3
lines changed

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { defineDeploymentTest } from './deployment.test.template.js';
2+
import { DataAndFunctionTestProjectCreator } from '../../test-project-setup/data_and_function_project.js';
3+
4+
defineDeploymentTest(new DataAndFunctionTestProjectCreator());
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { defineSandboxTest } from './sandbox.test.template.js';
2+
import { DataAndFunctionTestProjectCreator } from '../../test-project-setup/data_and_function_project.js';
3+
4+
defineSandboxTest(new DataAndFunctionTestProjectCreator());
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import { TestProjectBase } from './test_project_base.js';
2+
import fs from 'fs/promises';
3+
import { createEmptyAmplifyProject } from './create_empty_amplify_project.js';
4+
import { CloudFormationClient } from '@aws-sdk/client-cloudformation';
5+
import { TestProjectCreator } from './test_project_creator.js';
6+
import { AmplifyClient } from '@aws-sdk/client-amplify';
7+
import { BackendIdentifier } from '@aws-amplify/plugin-types';
8+
import { LambdaClient } from '@aws-sdk/client-lambda';
9+
import { DeployedResourcesFinder } from '../find_deployed_resource.js';
10+
import { generateClientConfig } from '@aws-amplify/client-config';
11+
import { CognitoIdentityProviderClient } from '@aws-sdk/client-cognito-identity-provider';
12+
import { SemVer } from 'semver';
13+
import crypto from 'node:crypto';
14+
import {
15+
ApolloClient,
16+
ApolloLink,
17+
HttpLink,
18+
InMemoryCache,
19+
} from '@apollo/client/core';
20+
import { AUTH_TYPE, createAuthLink } from 'aws-appsync-auth-link';
21+
import { gql } from 'graphql-tag';
22+
import assert from 'assert';
23+
import { NormalizedCacheObject } from '@apollo/client';
24+
import { e2eToolingClientConfig } from '../e2e_tooling_client_config.js';
25+
26+
// TODO: this is a work around
27+
// it seems like as of amplify v6 , some of the code only runs in the browser ...
28+
// see https://github.com/aws-amplify/amplify-js/issues/12751
29+
if (process.versions.node) {
30+
// node >= 20 now exposes crypto by default. This workaround is not needed: https://github.com/nodejs/node/pull/42083
31+
if (new SemVer(process.versions.node).major < 20) {
32+
// @ts-expect-error altering typing for global to make compiler happy is not worth the effort assuming this is temporary workaround
33+
globalThis.crypto = crypto;
34+
}
35+
}
36+
37+
/**
38+
* Creates the data and function test project.
39+
*/
40+
export class DataAndFunctionTestProjectCreator implements TestProjectCreator {
41+
readonly name = 'data-and-function';
42+
43+
/**
44+
* Creates project creator.
45+
*/
46+
constructor(
47+
private readonly cfnClient: CloudFormationClient = new CloudFormationClient(
48+
e2eToolingClientConfig
49+
),
50+
private readonly amplifyClient: AmplifyClient = new AmplifyClient(
51+
e2eToolingClientConfig
52+
),
53+
private readonly lambdaClient: LambdaClient = new LambdaClient(
54+
e2eToolingClientConfig
55+
),
56+
private readonly cognitoIdentityProviderClient: CognitoIdentityProviderClient = new CognitoIdentityProviderClient(
57+
e2eToolingClientConfig
58+
),
59+
private readonly resourceFinder: DeployedResourcesFinder = new DeployedResourcesFinder()
60+
) {}
61+
62+
createProject = async (e2eProjectDir: string): Promise<TestProjectBase> => {
63+
const { projectName, projectRoot, projectAmplifyDir } =
64+
await createEmptyAmplifyProject(this.name, e2eProjectDir);
65+
66+
const project = new DataAndFunctionTestProject(
67+
projectName,
68+
projectRoot,
69+
projectAmplifyDir,
70+
this.cfnClient,
71+
this.amplifyClient,
72+
this.lambdaClient,
73+
this.cognitoIdentityProviderClient,
74+
this.resourceFinder
75+
);
76+
await fs.cp(
77+
project.sourceProjectAmplifyDirURL,
78+
project.projectAmplifyDirPath,
79+
{
80+
recursive: true,
81+
}
82+
);
83+
return project;
84+
};
85+
}
86+
87+
/**
88+
* The data and function test project.
89+
*/
90+
class DataAndFunctionTestProject extends TestProjectBase {
91+
readonly sourceProjectDirPath = '../../src/test-projects/data-and-function';
92+
93+
readonly sourceProjectAmplifyDirSuffix = `${this.sourceProjectDirPath}/amplify`;
94+
95+
readonly sourceProjectAmplifyDirURL: URL = new URL(
96+
this.sourceProjectAmplifyDirSuffix,
97+
import.meta.url
98+
);
99+
100+
/**
101+
* Create a test project instance.
102+
*/
103+
constructor(
104+
name: string,
105+
projectDirPath: string,
106+
projectAmplifyDirPath: string,
107+
cfnClient: CloudFormationClient,
108+
amplifyClient: AmplifyClient,
109+
private readonly lambdaClient: LambdaClient = new LambdaClient(
110+
e2eToolingClientConfig
111+
),
112+
private readonly cognitoIdentityProviderClient: CognitoIdentityProviderClient = new CognitoIdentityProviderClient(
113+
e2eToolingClientConfig
114+
),
115+
private readonly resourceFinder: DeployedResourcesFinder = new DeployedResourcesFinder()
116+
) {
117+
super(
118+
name,
119+
projectDirPath,
120+
projectAmplifyDirPath,
121+
cfnClient,
122+
amplifyClient
123+
);
124+
}
125+
126+
override async assertPostDeployment(
127+
backendId: BackendIdentifier
128+
): Promise<void> {
129+
await super.assertPostDeployment(backendId);
130+
131+
const clientConfig = await generateClientConfig(backendId, '1.1');
132+
if (!clientConfig.data?.url) {
133+
throw new Error('Data and function project must include data');
134+
}
135+
if (!clientConfig.data.api_key) {
136+
throw new Error('Data and function project must include api_key');
137+
}
138+
139+
// const dataUrl = clientConfig.data?.url;
140+
141+
const httpLink = new HttpLink({ uri: clientConfig.data.url });
142+
const link = ApolloLink.from([
143+
createAuthLink({
144+
url: clientConfig.data.url,
145+
region: clientConfig.data.aws_region,
146+
auth: {
147+
type: AUTH_TYPE.API_KEY,
148+
apiKey: clientConfig.data.api_key,
149+
},
150+
}),
151+
// see https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/473#issuecomment-543029072
152+
httpLink,
153+
]);
154+
const apolloClient = new ApolloClient({
155+
link,
156+
cache: new InMemoryCache(),
157+
});
158+
159+
await this.assertDataFunctionCallSucceeds(apolloClient);
160+
await this.assertNoopWithImportCallSucceeds(apolloClient);
161+
}
162+
163+
private assertDataFunctionCallSucceeds = async (
164+
apolloClient: ApolloClient<NormalizedCacheObject>
165+
): Promise<void> => {
166+
const response = await apolloClient.query<number>({
167+
query: gql`
168+
query todoCount {
169+
todoCount
170+
}
171+
`,
172+
variables: {},
173+
});
174+
175+
assert.deepEqual(response.data, { todoCount: 0 });
176+
};
177+
178+
private assertNoopWithImportCallSucceeds = async (
179+
apolloClient: ApolloClient<NormalizedCacheObject>
180+
): Promise<void> => {
181+
const response = await apolloClient.query<number>({
182+
query: gql`
183+
query noopImport {
184+
noopImport
185+
}
186+
`,
187+
variables: {},
188+
});
189+
190+
assert.deepEqual(response.data, { noopImport: 'STATIC TEST RESPONSE' });
191+
};
192+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { defineBackend } from '@aws-amplify/backend';
2+
import { data } from './data/resource.js';
3+
import { todoCount } from './functions/todo-count/resource.js';
4+
import { noopImport } from './functions/noop-import/resource.js';
5+
6+
const backend = defineBackend({ data, todoCount, noopImport });
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { a, ClientSchema, defineData } from '@aws-amplify/backend';
2+
import { todoCount } from '../functions/todo-count/resource.js';
3+
import { noopImport } from '../functions/noop-import/resource.js';
4+
5+
const schema = a
6+
.schema({
7+
Todo: a
8+
.model({
9+
title: a.string().required(),
10+
done: a.boolean().default(false), // default value is false
11+
})
12+
.authorization((allow) => [allow.publicApiKey()]),
13+
todoCount: a
14+
.query()
15+
.arguments({})
16+
.returns(a.integer())
17+
.handler(a.handler.function(todoCount))
18+
.authorization((allow) => [allow.publicApiKey()]),
19+
noopImport: a
20+
.query()
21+
.arguments({})
22+
.returns(a.string())
23+
.handler(a.handler.function(noopImport))
24+
.authorization((allow) => [allow.publicApiKey()]),
25+
})
26+
.authorization((allow) => [
27+
allow.resource(todoCount),
28+
allow.resource(noopImport),
29+
]);
30+
31+
export type Schema = ClientSchema<typeof schema>;
32+
33+
export const data = defineData({
34+
schema,
35+
authorizationModes: {
36+
defaultAuthorizationMode: 'apiKey',
37+
// API Key is used for a.allow.public() rules
38+
apiKeyAuthorizationMode: {
39+
expiresInDays: 30,
40+
},
41+
},
42+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { Handler } from 'aws-lambda';
2+
import { Amplify } from 'aws-amplify';
3+
import { generateClient } from 'aws-amplify/data';
4+
import type { Schema } from '../../data/resource.js';
5+
import { getAmplifyDataClientConfig } from '@aws-amplify/backend/function/runtime';
6+
// @ts-ignore
7+
import { env } from '$amplify/env/noop-import.js';
8+
import { S3Client } from '@aws-sdk/client-s3';
9+
10+
const { resourceConfig, libraryOptions } = await getAmplifyDataClientConfig(
11+
env
12+
);
13+
14+
Amplify.configure(resourceConfig, libraryOptions);
15+
16+
const client = generateClient<Schema>();
17+
18+
export const handler: Handler = async () => {
19+
const _s3Client = new S3Client();
20+
const _todos = await client.models.Todo.list();
21+
return 'STATIC TEST RESPONSE';
22+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { defineFunction } from '@aws-amplify/backend';
2+
3+
export const noopImport = defineFunction({
4+
name: 'noop-import',
5+
entry: './handler.ts',
6+
timeoutSeconds: 30,
7+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { Handler } from 'aws-lambda';
2+
import { Amplify } from 'aws-amplify';
3+
import { generateClient } from 'aws-amplify/data';
4+
import type { Schema } from '../../data/resource.js';
5+
import { getAmplifyDataClientConfig } from '@aws-amplify/backend/function/runtime';
6+
// @ts-ignore
7+
import { env } from '$amplify/env/todo-count.js';
8+
9+
const { resourceConfig, libraryOptions } = await getAmplifyDataClientConfig(
10+
env
11+
);
12+
13+
Amplify.configure(resourceConfig, libraryOptions);
14+
15+
const client = generateClient<Schema>();
16+
17+
export const handler: Handler = async () => {
18+
const todos = await client.models.Todo.list();
19+
return todos.data.length;
20+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { defineFunction } from '@aws-amplify/backend';
2+
3+
export const todoCount = defineFunction({
4+
name: 'todo-count',
5+
entry: './handler.ts',
6+
timeoutSeconds: 30,
7+
});

0 commit comments

Comments
 (0)