Skip to content

Commit 42432b8

Browse files
feat(gen2-migration): gen2 test script integration (#14718)
* feat: gen2 test script integration * chore: adding all apps' gen2 test scripts * Potential fix for code scanning alert no. 2161: Superfluous trailing arguments Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Potential fix for code scanning alert no. 2162: Superfluous trailing arguments Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Potential fix for code scanning alert no. 2189: Syntax error Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update cli.ts * Update cli.ts * chore: syntax fix --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
1 parent a5fd8f7 commit 42432b8

8 files changed

Lines changed: 650 additions & 77 deletions

File tree

amplify-migration-apps/_test-common/signup.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,20 @@ function getErrorMessage(error: unknown): string {
3232
}
3333

3434
function resolveSigninIdentifier(usernameAttributes: string[]): SigninIdentifier {
35-
if (usernameAttributes.includes('PHONE_NUMBER')) return 'phone';
36-
if (usernameAttributes.includes('EMAIL')) return 'email';
35+
const normalized = usernameAttributes.map((a) => a.toUpperCase());
36+
if (normalized.includes('PHONE_NUMBER') || normalized.includes('PHONE')) return 'phone';
37+
if (normalized.includes('EMAIL')) return 'email';
3738
return 'username';
3839
}
3940

4041
function resolveSignupAttributes(signupAttributes: string[]): SignupAttribute[] {
4142
const mapping: Record<string, SignupAttribute> = {
4243
EMAIL: 'email',
4344
PHONE_NUMBER: 'phone',
45+
PHONE: 'phone',
4446
USERNAME: 'username',
4547
};
46-
const mapped = signupAttributes.map((attr) => mapping[attr]).filter((a): a is SignupAttribute => a !== undefined);
48+
const mapped = signupAttributes.map((attr) => mapping[attr.toUpperCase()]).filter((a): a is SignupAttribute => a !== undefined);
4749
return mapped.length > 0 ? mapped : ['email'];
4850
}
4951

@@ -154,11 +156,14 @@ function generateTestPassword(): string {
154156
* Returns the username to use for signIn.
155157
*/
156158
export async function provisionTestUser(config: AmplifyConfig): Promise<{ signinValue: string; testUser: TestUser }> {
157-
const { aws_user_pools_id: userPoolId, aws_cognito_region: region } = config;
159+
// Support both Gen1 (aws_user_pools_id) and Gen2 (auth.user_pool_id) config formats
160+
const gen2Auth = (config as any)?.auth;
161+
const userPoolId = config.aws_user_pools_id ?? gen2Auth?.user_pool_id;
162+
const region = config.aws_cognito_region ?? gen2Auth?.aws_region;
158163

159164
const resolved: ResolvedAuthConfig = {
160-
signinIdentifier: resolveSigninIdentifier(config.aws_cognito_username_attributes ?? []),
161-
signupAttributes: resolveSignupAttributes(config.aws_cognito_signup_attributes ?? []),
165+
signinIdentifier: resolveSigninIdentifier(config.aws_cognito_username_attributes ?? gen2Auth?.username_attributes ?? []),
166+
signupAttributes: resolveSignupAttributes(config.aws_cognito_signup_attributes ?? gen2Auth?.standard_required_attributes ?? []),
162167
};
163168

164169
const credentials = generateCredentials(resolved);
Lines changed: 104 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
/**
22
* Gen2 Test Script for Discussions App
33
*
4-
* This script tests all functionality for Gen2 Amplify configuration.
5-
* IMPORTANT: Update TEST_USER credentials before running authenticated tests.
4+
* This script tests all functionality for Amplify Gen2:
5+
* 1. GraphQL Queries (Topics, Posts, Comments)
6+
* 2. Topic CRUD Operations
7+
* 3. Post CRUD Operations
8+
* 4. Comment CRUD Operations
9+
* 5. User Activity Tracking
10+
* 6. S3 Storage (Avatars)
11+
* 7. Bookmarks DDB
12+
* 8. Cleanup (Delete Test Data)
13+
*
14+
* Credentials are provisioned automatically via Cognito AdminCreateUser.
615
*/
716

817
// Polyfill crypto for Node.js environment (required for Amplify Auth)
@@ -12,59 +21,102 @@ if (typeof globalThis.crypto === 'undefined') {
1221
}
1322

1423
import { Amplify } from 'aws-amplify';
24+
import { signIn, signOut, getCurrentUser } from 'aws-amplify/auth';
1525
import amplifyconfig from './src/amplify_outputs.json';
16-
import { createTestRunner, runAllTests } from './test-utils';
17-
import { getTopic, listTopics, getPost, listPosts, getComment, listComments, fetchUserActivity } from './src/graphql/queries';
18-
import {
19-
createTopic,
20-
updateTopic,
21-
deleteTopic,
22-
createPost,
23-
updatePost,
24-
deletePost,
25-
createComment,
26-
updateComment,
27-
deleteComment,
28-
} from './src/graphql/mutations';
29-
30-
// Configure Amplify with Gen2 configuration
26+
import { TestRunner } from '../_test-common/test-apps-test-utils';
27+
import { provisionTestUser } from '../_test-common/signup';
28+
import { createTestFunctions, createTestOrchestrator } from './test-utils';
29+
30+
// Configure Amplify with Gen2 outputs
3131
Amplify.configure(amplifyconfig);
3232

3333
// ============================================================
34-
// CONFIGURATION - Update with your test user credentials
34+
// Main Test Execution
3535
// ============================================================
36-
const TEST_USER = {
37-
username: 'YOUR_USERNAME_HERE', // Phone number format
38-
password: 'YOUR_PASSWORD_HERE',
39-
};
4036

41-
// ============================================================
42-
// Main Entry Point
43-
// ============================================================
44-
const { runTest, printSummary } = createTestRunner();
45-
46-
void runAllTests({
47-
queries: {
48-
getTopic,
49-
listTopics,
50-
getPost,
51-
listPosts,
52-
getComment,
53-
listComments,
54-
fetchUserActivity,
55-
},
56-
mutations: {
57-
createTopic,
58-
updateTopic,
59-
deleteTopic,
60-
createPost,
61-
updatePost,
62-
deletePost,
63-
createComment,
64-
updateComment,
65-
deleteComment,
66-
},
67-
testUser: TEST_USER,
68-
runTest,
69-
printSummary,
70-
});
37+
async function runAllTests(): Promise<void> {
38+
console.log('🚀 Starting Discussions App Gen2 Test Script\n');
39+
console.log('This script tests:');
40+
console.log(' 1. GraphQL Queries (Topics, Posts, Comments)');
41+
console.log(' 2. Topic CRUD Operations');
42+
console.log(' 3. Post CRUD Operations');
43+
console.log(' 4. Comment CRUD Operations');
44+
console.log(' 5. User Activity Tracking');
45+
console.log(' 6. S3 Storage (Avatars)');
46+
console.log(' 7. Bookmarks DDB');
47+
console.log(' 8. Cleanup (Delete Test Data)');
48+
49+
// Provision user via admin APIs, then sign in here so tokens stay in this module's Amplify scope
50+
const { signinValue, testUser } = await provisionTestUser(amplifyconfig);
51+
52+
try {
53+
await signIn({ username: signinValue, password: testUser.password });
54+
const currentUser = await getCurrentUser();
55+
console.log(`✅ Signed in as: ${currentUser.username}`);
56+
} catch (error: any) {
57+
console.error('❌ SignIn failed:', error.message || error);
58+
process.exit(1);
59+
}
60+
61+
const runner = new TestRunner();
62+
const testFunctions = createTestFunctions();
63+
const {
64+
runQueryTests,
65+
runTopicMutationTests,
66+
runPostMutationTests,
67+
runCommentMutationTests,
68+
runActivityTests,
69+
runStorageTests,
70+
runBookmarksTests,
71+
runCleanupTests,
72+
} = createTestOrchestrator(testFunctions, runner);
73+
74+
// Get current user ID for activity tests
75+
const currentUser = await getCurrentUser();
76+
77+
// Part 1: Query tests
78+
await runQueryTests();
79+
80+
// Part 2: Topic mutations
81+
const topicId = await runTopicMutationTests();
82+
83+
// Part 3: Post mutations (requires topic)
84+
let postId: string | null = null;
85+
if (topicId) {
86+
postId = await runPostMutationTests(topicId);
87+
}
88+
89+
// Part 4: Comment mutations (requires post)
90+
let commentId: string | null = null;
91+
if (postId) {
92+
commentId = await runCommentMutationTests(postId);
93+
}
94+
95+
// Part 5: Activity tests
96+
await runActivityTests(currentUser.userId);
97+
98+
// Part 6: S3 Storage (Avatars)
99+
await runStorageTests();
100+
101+
// Part 7: Bookmarks DDB (requires a post to bookmark)
102+
if (postId) {
103+
await runBookmarksTests(currentUser.userId, postId);
104+
}
105+
106+
// Part 8: Cleanup
107+
await runCleanupTests(topicId, postId, commentId);
108+
109+
// Sign out
110+
try {
111+
await signOut();
112+
console.log('✅ Signed out successfully');
113+
} catch (error: any) {
114+
console.error('❌ Sign out error:', error.message || error);
115+
}
116+
117+
// Print summary and exit with appropriate code
118+
runner.printSummary();
119+
}
120+
121+
// Run all tests
122+
void runAllTests();
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/**
2+
* Gen2 Test Script for Fitness Tracker App
3+
*
4+
* This script tests all functionality for Amplify Gen2:
5+
* 1. Authenticated GraphQL Queries (requires auth)
6+
* 2. Authenticated GraphQL Mutations (requires auth)
7+
* 3. REST API Operations (nutrition logging)
8+
*
9+
* Credentials are provisioned automatically via Cognito AdminCreateUser.
10+
*/
11+
12+
// Polyfill crypto for Node.js environment (required for Amplify Auth)
13+
import { webcrypto } from 'crypto';
14+
if (typeof globalThis.crypto === 'undefined') {
15+
(globalThis as any).crypto = webcrypto;
16+
}
17+
18+
import { Amplify } from 'aws-amplify';
19+
import { signIn, signOut, getCurrentUser, fetchAuthSession } from 'aws-amplify/auth';
20+
import { parseAmplifyConfig } from 'aws-amplify/utils';
21+
import amplifyconfig from './src/amplify_outputs.json';
22+
import { SignatureV4 } from '@aws-sdk/signature-v4';
23+
import { HttpRequest } from '@aws-sdk/protocol-http';
24+
import { Sha256 } from '@aws-crypto/sha256-js';
25+
import { TestRunner } from '../_test-common/test-apps-test-utils';
26+
import { provisionTestUser } from '../_test-common/signup';
27+
import { createTestFunctions, createTestOrchestrator } from './test-utils';
28+
29+
// Configure Amplify with Gen2 outputs, merging REST API config
30+
const parsedConfig = parseAmplifyConfig(amplifyconfig);
31+
Amplify.configure({
32+
...parsedConfig,
33+
API: {
34+
...parsedConfig.API,
35+
REST: {
36+
...(amplifyconfig as any).custom?.API,
37+
},
38+
},
39+
});
40+
41+
// ============================================================
42+
// REST API Test Functions (Gen2-specific, uses SigV4 signing)
43+
// ============================================================
44+
45+
/**
46+
* Make a signed REST API request using AWS SigV4.
47+
* Gen2 REST APIs require manual signing since Amplify's post() helper
48+
* has signing issues in Node.js environments.
49+
*/
50+
async function makeSignedRequest(
51+
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
52+
path: string,
53+
body?: any,
54+
): Promise<any> {
55+
const session = await fetchAuthSession();
56+
const credentials = session.credentials;
57+
58+
if (!credentials) {
59+
throw new Error('No credentials available');
60+
}
61+
62+
const apiConfigs = (amplifyconfig as any).custom.API;
63+
const apiName = Object.keys(apiConfigs)[0];
64+
const apiConfig = apiConfigs[apiName];
65+
let endpoint = apiConfig.endpoint;
66+
const region = apiConfig.region;
67+
68+
if (endpoint.endsWith('/')) {
69+
endpoint = endpoint.slice(0, -1);
70+
}
71+
72+
const normalizedPath = path.startsWith('/') ? path : '/' + path;
73+
const url = new URL(endpoint + normalizedPath);
74+
75+
console.log(' 🔗 Request URL:', url.toString());
76+
77+
const request = new HttpRequest({
78+
method,
79+
protocol: url.protocol,
80+
hostname: url.hostname,
81+
path: url.pathname + url.search,
82+
headers: {
83+
'Content-Type': 'application/json',
84+
host: url.hostname,
85+
},
86+
body: body ? JSON.stringify(body) : undefined,
87+
});
88+
89+
const signer = new SignatureV4({
90+
credentials,
91+
region,
92+
service: 'execute-api',
93+
sha256: Sha256,
94+
});
95+
96+
const signedRequest = await signer.sign(request);
97+
98+
const response = await fetch(url.toString(), {
99+
method: signedRequest.method,
100+
headers: signedRequest.headers,
101+
body: signedRequest.body,
102+
});
103+
104+
const responseText = await response.text();
105+
console.log(' 📥 Response status:', response.status);
106+
107+
if (!response.ok) {
108+
throw new Error(`HTTP ${response.status}: ${responseText}`);
109+
}
110+
111+
return JSON.parse(responseText);
112+
}
113+
114+
async function testNutritionLogAPI(): Promise<void> {
115+
console.log('\n🍔 Testing Gen2 REST API - POST /nutrition/log...');
116+
const user = await getCurrentUser();
117+
118+
const requestBody = {
119+
userName: user.username,
120+
content: `Test nutrition log via Gen2 REST API - Pizza and salad - ${Date.now()}`,
121+
};
122+
123+
const response = await makeSignedRequest('POST', 'nutrition/log', requestBody);
124+
console.log('✅ Gen2 REST API Response:', response);
125+
console.log(' Message:', response.message);
126+
}
127+
128+
// ============================================================
129+
// Main Test Execution
130+
// ============================================================
131+
132+
async function runAllTests(): Promise<void> {
133+
console.log('🚀 Starting Gen2 Test Script for Fitness Tracker\n');
134+
console.log('This script tests:');
135+
console.log(' 1. Authenticated GraphQL Queries');
136+
console.log(' 2. Authenticated GraphQL Mutations');
137+
console.log(' 3. REST API Operations (Nutrition Logging)');
138+
139+
// Provision user via admin APIs, then sign in here so tokens stay in this module's Amplify scope
140+
const { signinValue, testUser } = await provisionTestUser(amplifyconfig);
141+
142+
try {
143+
await signIn({ username: signinValue, password: testUser.password });
144+
const currentUser = await getCurrentUser();
145+
console.log(`✅ Signed in as: ${currentUser.username}`);
146+
} catch (error: any) {
147+
console.error('❌ SignIn failed:', error.message || error);
148+
process.exit(1);
149+
}
150+
151+
const runner = new TestRunner();
152+
const testFunctions = createTestFunctions();
153+
const { runQueryTests, runMutationTests } = createTestOrchestrator(testFunctions, runner);
154+
155+
// Part 1: Queries
156+
await runQueryTests();
157+
158+
// Part 2: Mutations
159+
await runMutationTests();
160+
161+
// Part 3: REST API (gen2-specific, uses SigV4 signing)
162+
console.log('\n' + '='.repeat(50));
163+
console.log('🌐 PART 3: REST API Operations');
164+
console.log('='.repeat(50));
165+
166+
await runner.runTest('nutritionLogAPI', testNutritionLogAPI);
167+
168+
console.log('\n💡 Note: The REST API creates meals directly in DynamoDB.');
169+
console.log(' Check your app to see the logged nutrition data!');
170+
171+
// Sign out
172+
try {
173+
await signOut();
174+
console.log('✅ Signed out successfully');
175+
} catch (error: any) {
176+
console.error('❌ Sign out error:', error.message || error);
177+
}
178+
179+
// Print summary and exit with appropriate code
180+
runner.printSummary();
181+
}
182+
183+
// Run all tests
184+
void runAllTests();

0 commit comments

Comments
 (0)