Skip to content

Commit d207e68

Browse files
committed
feat(cli): add S3 avatars and bookmarks DDB tests to discussions test script
Extends the discussions gen1 test script to cover all stateful resources that get refactored during migration: S3 avatars bucket (upload/getUrl/remove) and bookmarks DynamoDB table (put/get/delete via AWS SDK). Verified with full forward→rollback→forward round-trip on live app.
1 parent 150d44e commit d207e68

3 files changed

Lines changed: 192 additions & 6 deletions

File tree

amplify-migration-apps/discussions/gen1-test-script.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
* 3. Post CRUD Operations
88
* 4. Comment CRUD Operations
99
* 5. User Activity Tracking
10-
* 6. Cleanup (Delete Test Data)
10+
* 6. S3 Storage (Avatars)
11+
* 7. Bookmarks DDB
12+
* 8. Cleanup (Delete Test Data)
1113
*
1214
* Credentials are provisioned automatically via Cognito AdminCreateUser + AdminSetUserPassword.
1315
*/
@@ -40,7 +42,9 @@ async function runAllTests(): Promise<void> {
4042
console.log(' 3. Post CRUD Operations');
4143
console.log(' 4. Comment CRUD Operations');
4244
console.log(' 5. User Activity Tracking');
43-
console.log(' 6. Cleanup (Delete Test Data)');
45+
console.log(' 6. S3 Storage (Avatars)');
46+
console.log(' 7. Bookmarks DDB');
47+
console.log(' 8. Cleanup (Delete Test Data)');
4448

4549
// Provision user via admin APIs, then sign in here so tokens stay in this module's Amplify scope
4650
const { signinValue, testUser } = await provisionTestUser(amplifyconfig);
@@ -57,8 +61,16 @@ async function runAllTests(): Promise<void> {
5761

5862
const runner = new TestRunner();
5963
const testFunctions = createTestFunctions();
60-
const { runQueryTests, runTopicMutationTests, runPostMutationTests, runCommentMutationTests, runActivityTests, runCleanupTests } =
61-
createTestOrchestrator(testFunctions, runner);
64+
const {
65+
runQueryTests,
66+
runTopicMutationTests,
67+
runPostMutationTests,
68+
runCommentMutationTests,
69+
runActivityTests,
70+
runStorageTests,
71+
runBookmarksTests,
72+
runCleanupTests,
73+
} = createTestOrchestrator(testFunctions, runner);
6274

6375
// Get current user ID for activity tests
6476
const currentUser = await getCurrentUser();
@@ -84,7 +96,15 @@ async function runAllTests(): Promise<void> {
8496
// Part 5: Activity tests
8597
await runActivityTests(currentUser.userId);
8698

87-
// Part 6: Cleanup
99+
// Part 6: S3 Storage (Avatars)
100+
await runStorageTests();
101+
102+
// Part 7: Bookmarks DDB (requires a post to bookmark)
103+
if (postId) {
104+
await runBookmarksTests(currentUser.userId, postId);
105+
}
106+
107+
// Part 8: Cleanup
88108
await runCleanupTests(topicId, postId, commentId);
89109

90110
// Sign out

amplify-migration-apps/discussions/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
"vite": "^7.2.2"
2121
},
2222
"dependencies": {
23+
"@aws-sdk/client-dynamodb": "^3.936.0",
24+
"@aws-sdk/lib-dynamodb": "^3.936.0",
2325
"aws-amplify": "^6.15.8"
2426
}
2527
}

amplify-migration-apps/discussions/test-utils.ts

Lines changed: 165 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import { Amplify } from 'aws-amplify';
44
import { generateClient } from 'aws-amplify/api';
55
import { getCurrentUser } from 'aws-amplify/auth';
6+
import { uploadData, getUrl, remove } from 'aws-amplify/storage';
7+
import { DynamoDBClient, ListTablesCommand } from '@aws-sdk/client-dynamodb';
8+
import { DynamoDBDocumentClient, PutCommand, GetCommand, DeleteCommand } from '@aws-sdk/lib-dynamodb';
69
import { getTopic, listTopics, getPost, listPosts, getComment, listComments, fetchUserActivity } from './src/graphql/queries';
710
import {
811
createTopic,
@@ -319,6 +322,115 @@ export function createTestFunctions() {
319322
console.log('✅ Deleted comment:', deleted.content?.substring(0, 30) + '...');
320323
}
321324

325+
// ============================================================
326+
// S3 Avatar Test Functions
327+
// ============================================================
328+
329+
async function testUploadAvatar(): Promise<string | null> {
330+
console.log('\n📤 Testing uploadData (S3 avatar upload)...');
331+
// 1x1 transparent PNG
332+
const testImageBase64 =
333+
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
334+
const imageBuffer = Buffer.from(testImageBase64, 'base64');
335+
const fileName = `test-avatar-${Date.now()}.png`;
336+
337+
console.log(` Uploading to: ${fileName}`);
338+
console.log(` File size: ${imageBuffer.length} bytes`);
339+
340+
const result = await uploadData({
341+
key: fileName,
342+
data: imageBuffer,
343+
options: { contentType: 'image/png' },
344+
}).result;
345+
346+
console.log('✅ Upload successful!');
347+
console.log(' Key:', result.key);
348+
return result.key;
349+
}
350+
351+
async function testGetAvatarUrl(avatarKey: string): Promise<string | null> {
352+
console.log(`\n🔗 Testing getUrl (S3 signed URL)...`);
353+
console.log(` Avatar key: ${avatarKey}`);
354+
355+
const result = await getUrl({
356+
key: avatarKey,
357+
options: { expiresIn: 3600 },
358+
});
359+
console.log('✅ Got signed URL!');
360+
console.log(' URL:', result.url.toString().substring(0, 80) + '...');
361+
return result.url.toString();
362+
}
363+
364+
async function testRemoveAvatar(avatarKey: string): Promise<void> {
365+
console.log(`\n🗑️ Testing remove (S3 avatar delete)...`);
366+
console.log(` Avatar key: ${avatarKey}`);
367+
368+
await remove({ key: avatarKey });
369+
console.log('✅ Avatar removed successfully!');
370+
}
371+
372+
// ============================================================
373+
// Bookmarks DDB Test Functions
374+
// ============================================================
375+
376+
async function testCreateBookmark(tableName: string, userId: string, postId: string): Promise<void> {
377+
console.log('\n🔖 Testing PutItem (create bookmark)...');
378+
console.log(` Table: ${tableName}`);
379+
console.log(` userId: ${userId.substring(0, 8)}..., postId: ${postId.substring(0, 8)}...`);
380+
381+
const ddbClient = DynamoDBDocumentClient.from(
382+
new DynamoDBClient({ region: (amplifyconfig as any).aws_project_region }),
383+
);
384+
await ddbClient.send(
385+
new PutCommand({
386+
TableName: tableName,
387+
Item: { userId, postId, createdAt: new Date().toISOString() },
388+
}),
389+
);
390+
console.log('✅ Bookmark created!');
391+
}
392+
393+
async function testGetBookmark(tableName: string, userId: string, postId: string): Promise<void> {
394+
console.log('\n🔖 Testing GetItem (read bookmark)...');
395+
console.log(` Table: ${tableName}`);
396+
console.log(` userId: ${userId.substring(0, 8)}..., postId: ${postId.substring(0, 8)}...`);
397+
398+
const ddbClient = DynamoDBDocumentClient.from(
399+
new DynamoDBClient({ region: (amplifyconfig as any).aws_project_region }),
400+
);
401+
const result = await ddbClient.send(
402+
new GetCommand({
403+
TableName: tableName,
404+
Key: { userId, postId },
405+
}),
406+
);
407+
if (!result.Item) {
408+
throw new Error('Bookmark not found');
409+
}
410+
console.log('✅ Bookmark found:', {
411+
userId: result.Item.userId.substring(0, 8) + '...',
412+
postId: result.Item.postId.substring(0, 8) + '...',
413+
createdAt: result.Item.createdAt,
414+
});
415+
}
416+
417+
async function testDeleteBookmark(tableName: string, userId: string, postId: string): Promise<void> {
418+
console.log('\n🗑️ Testing DeleteItem (remove bookmark)...');
419+
console.log(` Table: ${tableName}`);
420+
console.log(` userId: ${userId.substring(0, 8)}..., postId: ${postId.substring(0, 8)}...`);
421+
422+
const ddbClient = DynamoDBDocumentClient.from(
423+
new DynamoDBClient({ region: (amplifyconfig as any).aws_project_region }),
424+
);
425+
await ddbClient.send(
426+
new DeleteCommand({
427+
TableName: tableName,
428+
Key: { userId, postId },
429+
}),
430+
);
431+
console.log('✅ Bookmark deleted!');
432+
}
433+
322434
return {
323435
testListTopics,
324436
testGetTopic,
@@ -336,9 +448,33 @@ export function createTestFunctions() {
336448
testCreateComment,
337449
testUpdateComment,
338450
testDeleteComment,
451+
testUploadAvatar,
452+
testGetAvatarUrl,
453+
testRemoveAvatar,
454+
testCreateBookmark,
455+
testGetBookmark,
456+
testDeleteBookmark,
339457
};
340458
}
341459

460+
// ============================================================
461+
// Bookmarks Table Discovery
462+
// ============================================================
463+
464+
export async function discoverBookmarksTable(region: string): Promise<string> {
465+
const client = new DynamoDBClient({ region });
466+
const result = await client.send(new ListTablesCommand({}));
467+
const tables = result.TableNames ?? [];
468+
const bookmarksTables = tables.filter((t) => t.startsWith('bookmarks-'));
469+
if (bookmarksTables.length === 0) {
470+
throw new Error('No bookmarks table found. Expected a DynamoDB table starting with "bookmarks-".');
471+
}
472+
if (bookmarksTables.length > 1) {
473+
console.log(`⚠️ Found multiple bookmarks tables: ${bookmarksTables.join(', ')}. Using first one.`);
474+
}
475+
return bookmarksTables[0];
476+
}
477+
342478
// ============================================================
343479
// Shared Test Orchestration Functions
344480
// ============================================================
@@ -440,9 +576,35 @@ export function createTestOrchestrator(testFunctions: ReturnType<typeof createTe
440576
await runner.runTest('fetchUserActivity', () => testFunctions.testFetchUserActivity(userId));
441577
}
442578

579+
async function runStorageTests(): Promise<void> {
580+
console.log('\n' + '='.repeat(60));
581+
console.log('📸 PART 6: S3 Storage (Avatars)');
582+
console.log('='.repeat(60));
583+
584+
const avatarKey = await runner.runTest('uploadAvatar', testFunctions.testUploadAvatar);
585+
if (avatarKey) {
586+
await runner.runTest('getAvatarUrl', () => testFunctions.testGetAvatarUrl(avatarKey));
587+
await runner.runTest('removeAvatar', () => testFunctions.testRemoveAvatar(avatarKey));
588+
}
589+
}
590+
591+
async function runBookmarksTests(userId: string, postId: string): Promise<void> {
592+
console.log('\n' + '='.repeat(60));
593+
console.log('🔖 PART 7: Bookmarks DDB');
594+
console.log('='.repeat(60));
595+
596+
const region = (amplifyconfig as any).aws_project_region || 'us-east-1';
597+
const tableName = await discoverBookmarksTable(region);
598+
console.log(` Discovered bookmarks table: ${tableName}`);
599+
600+
await runner.runTest('createBookmark', () => testFunctions.testCreateBookmark(tableName, userId, postId));
601+
await runner.runTest('getBookmark', () => testFunctions.testGetBookmark(tableName, userId, postId));
602+
await runner.runTest('deleteBookmark', () => testFunctions.testDeleteBookmark(tableName, userId, postId));
603+
}
604+
443605
async function runCleanupTests(topicId: string | null, postId: string | null, commentId: string | null): Promise<void> {
444606
console.log('\n' + '='.repeat(60));
445-
console.log('🧹 PART 6: Cleanup (Delete Test Data)');
607+
console.log('🧹 PART 8: Cleanup (Delete Test Data)');
446608
console.log('='.repeat(60));
447609

448610
// Delete in reverse order of creation (comments -> posts -> topics)
@@ -457,6 +619,8 @@ export function createTestOrchestrator(testFunctions: ReturnType<typeof createTe
457619
runPostMutationTests,
458620
runCommentMutationTests,
459621
runActivityTests,
622+
runStorageTests,
623+
runBookmarksTests,
460624
runCleanupTests,
461625
};
462626
}

0 commit comments

Comments
 (0)