import { describe, it, expect, beforeEach, afterEach } from "bun:test"; describe('Signin Migration Logic (Unit Tests)', () => { // Test the deduplication algorithm independently it('should correctly identify and remove duplicates keeping earliest', () => { // Mock completion data structure type MockCompletion = { id: string; anonymousId: string; date: string; guessCount: number; completedAt: Date; }; // Test data: multiple completions on same date const allUserCompletions: MockCompletion[] = [ { id: 'comp1', anonymousId: 'user123', date: '2024-01-01', guessCount: 4, completedAt: new Date('2024-01-01T08:00:00Z') // Earliest }, { id: 'comp2', anonymousId: 'user123', date: '2024-01-01', guessCount: 2, completedAt: new Date('2024-01-01T14:00:00Z') // Later }, { id: 'comp3', anonymousId: 'user123', date: '2024-01-01', guessCount: 6, completedAt: new Date('2024-01-01T20:00:00Z') // Latest }, { id: 'comp4', anonymousId: 'user123', date: '2024-01-02', guessCount: 3, completedAt: new Date('2024-01-02T09:00:00Z') // Unique date } ]; // Implement the deduplication logic from signin server action const dateGroups = new Map(); for (const completion of allUserCompletions) { const date = completion.date; if (!dateGroups.has(date)) { dateGroups.set(date, []); } dateGroups.get(date)!.push(completion); } // Process dates with duplicates const duplicateIds: string[] = []; const keptEntries: MockCompletion[] = []; for (const [date, completions] of dateGroups) { if (completions.length > 1) { // Sort by completedAt timestamp (earliest first) completions.sort((a, b) => a.completedAt.getTime() - b.completedAt.getTime()); // Keep the first (earliest), mark the rest for deletion const toKeep = completions[0]; const toDelete = completions.slice(1); keptEntries.push(toKeep); duplicateIds.push(...toDelete.map(c => c.id)); } else { // Single entry for this date, keep it keptEntries.push(completions[0]); } } // Verify the logic worked correctly expect(duplicateIds).toHaveLength(2); // comp2 and comp3 should be deleted expect(duplicateIds).toContain('comp2'); expect(duplicateIds).toContain('comp3'); expect(duplicateIds).not.toContain('comp1'); // comp1 should be kept (earliest) expect(duplicateIds).not.toContain('comp4'); // comp4 should be kept (unique date) // Verify kept entries expect(keptEntries).toHaveLength(2); // Check that the earliest entry for 2024-01-01 was kept const jan1Entry = keptEntries.find(e => e.date === '2024-01-01'); expect(jan1Entry).toBeTruthy(); expect(jan1Entry!.id).toBe('comp1'); // Earliest timestamp expect(jan1Entry!.guessCount).toBe(4); expect(jan1Entry!.completedAt.getTime()).toBe(new Date('2024-01-01T08:00:00Z').getTime()); // Check that unique date entry was preserved const jan2Entry = keptEntries.find(e => e.date === '2024-01-02'); expect(jan2Entry).toBeTruthy(); expect(jan2Entry!.id).toBe('comp4'); }); it('should handle no duplicates correctly', () => { type MockCompletion = { id: string; anonymousId: string; date: string; guessCount: number; completedAt: Date; }; // Test data: all unique dates const allUserCompletions: MockCompletion[] = [ { id: 'comp1', anonymousId: 'user123', date: '2024-01-01', guessCount: 4, completedAt: new Date('2024-01-01T08:00:00Z') }, { id: 'comp2', anonymousId: 'user123', date: '2024-01-02', guessCount: 2, completedAt: new Date('2024-01-02T14:00:00Z') } ]; // Run deduplication logic const dateGroups = new Map(); for (const completion of allUserCompletions) { if (!dateGroups.has(completion.date)) { dateGroups.set(completion.date, []); } dateGroups.get(completion.date)!.push(completion); } const duplicateIds: string[] = []; for (const [date, completions] of dateGroups) { if (completions.length > 1) { completions.sort((a, b) => a.completedAt.getTime() - b.completedAt.getTime()); const toDelete = completions.slice(1); duplicateIds.push(...toDelete.map(c => c.id)); } } // Should find no duplicates expect(duplicateIds).toHaveLength(0); }); it('should handle edge case with same timestamp', () => { type MockCompletion = { id: string; anonymousId: string; date: string; guessCount: number; completedAt: Date; }; // Edge case: same completion time (very unlikely but possible) const sameTime = new Date('2024-01-01T08:00:00Z'); const allUserCompletions: MockCompletion[] = [ { id: 'comp1', anonymousId: 'user123', date: '2024-01-01', guessCount: 3, completedAt: sameTime }, { id: 'comp2', anonymousId: 'user123', date: '2024-01-01', guessCount: 5, completedAt: sameTime } ]; // Run deduplication logic const dateGroups = new Map(); for (const completion of allUserCompletions) { if (!dateGroups.has(completion.date)) { dateGroups.set(completion.date, []); } dateGroups.get(completion.date)!.push(completion); } const duplicateIds: string[] = []; for (const [date, completions] of dateGroups) { if (completions.length > 1) { completions.sort((a, b) => a.completedAt.getTime() - b.completedAt.getTime()); const toDelete = completions.slice(1); duplicateIds.push(...toDelete.map(c => c.id)); } } // Should still remove one duplicate (deterministically based on array order) expect(duplicateIds).toHaveLength(1); // Since they have the same timestamp, it keeps the first one in the sorted array expect(duplicateIds[0]).toBe('comp2'); // Second entry gets removed }); it('should validate migration condition logic', () => { // Test the condition check that determines when migration should occur const testCases = [ { anonymousId: 'device2-id', userId: 'device1-id', shouldMigrate: true, description: 'Different IDs should trigger migration' }, { anonymousId: 'same-id', userId: 'same-id', shouldMigrate: false, description: 'Same IDs should not trigger migration' }, { anonymousId: null as any, userId: 'user-id', shouldMigrate: false, description: 'Null anonymous ID should not trigger migration' }, { anonymousId: undefined as any, userId: 'user-id', shouldMigrate: false, description: 'Undefined anonymous ID should not trigger migration' }, { anonymousId: '', userId: 'user-id', shouldMigrate: false, description: 'Empty anonymous ID should not trigger migration' } ]; for (const testCase of testCases) { // This is the exact condition from signin/+page.server.ts const shouldMigrate = !!(testCase.anonymousId && testCase.anonymousId !== testCase.userId); expect(shouldMigrate).toBe(testCase.shouldMigrate); } }); });