/** * Data Migration Tests * Validates that data migration scripts work correctly * Tests data transformation, integrity, and edge cases */ import { PrismaClient } from '@prisma/client' import { setupTestEnvironment, createTestPrismaClient, initializeTestDatabase, cleanupTestDatabase, createSampleNotes, createSampleAINotes, verifyDataIntegrity, measureExecutionTime } from './setup' describe('Data Migration Tests', () => { let prisma: PrismaClient beforeAll(async () => { await setupTestEnvironment() prisma = createTestPrismaClient() await initializeTestDatabase(prisma) }) afterAll(async () => { await cleanupTestDatabase(prisma) }) describe('Empty Database Migration', () => { test('should migrate empty database successfully', async () => { // Verify database is empty const noteCount = await prisma.note.count() expect(noteCount).toBe(0) // Data migration should handle empty database gracefully // No data should be created or lost expect(noteCount).toBe(0) }) }) describe('Basic Data Migration', () => { beforeEach(async () => { // Clean up before each test await prisma.note.deleteMany({}) await prisma.aiFeedback.deleteMany({}) }) test('should migrate basic notes without AI fields', async () => { // Create sample notes (simulating pre-migration data) await createSampleNotes(prisma, 10) // Verify notes are created const noteCount = await prisma.note.count() expect(noteCount).toBe(10) // All notes should have null AI fields (backward compatibility) const notes = await prisma.note.findMany() notes.forEach(note => { expect(note.autoGenerated).toBeNull() expect(note.aiProvider).toBeNull() expect(note.aiConfidence).toBeNull() expect(note.language).toBeNull() expect(note.languageConfidence).toBeNull() expect(note.lastAiAnalysis).toBeNull() }) }) test('should preserve existing note data during migration', async () => { // Create a note with all fields const originalNote = await prisma.note.create({ data: { title: 'Original Note', content: 'Original content', color: 'blue', isPinned: true, isArchived: false, type: 'text', size: 'medium', userId: 'test-user-id', order: 0 } }) // Simulate migration by querying the note const noteAfterMigration = await prisma.note.findUnique({ where: { id: originalNote.id } }) // Verify all original fields are preserved expect(noteAfterMigration?.title).toBe('Original Note') expect(noteAfterMigration?.content).toBe('Original content') expect(noteAfterMigration?.color).toBe('blue') expect(noteAfterMigration?.isPinned).toBe(true) expect(noteAfterMigration?.isArchived).toBe(false) expect(noteAfterMigration?.type).toBe('text') expect(noteAfterMigration?.size).toBe('medium') expect(noteAfterMigration?.order).toBe(0) }) }) describe('AI Fields Data Migration', () => { test('should handle notes with all AI fields populated', async () => { const testNote = await prisma.note.create({ data: { title: 'AI Test Note', content: 'Test content', userId: 'test-user-id', autoGenerated: true, aiProvider: 'openai', aiConfidence: 95, language: 'en', languageConfidence: 0.98, lastAiAnalysis: new Date() } }) // Verify AI fields are correctly stored expect(testNote.autoGenerated).toBe(true) expect(testNote.aiProvider).toBe('openai') expect(testNote.aiConfidence).toBe(95) expect(testNote.language).toBe('en') expect(testNote.languageConfidence).toBe(0.98) expect(testNote.lastAiAnalysis).toBeDefined() }) test('should handle notes with partial AI fields', async () => { // Create note with only some AI fields const note1 = await prisma.note.create({ data: { title: 'Partial AI Note 1', content: 'Test content', userId: 'test-user-id', autoGenerated: true, aiProvider: 'ollama' // Other AI fields are null } }) expect(note1.autoGenerated).toBe(true) expect(note1.aiProvider).toBe('ollama') expect(note1.aiConfidence).toBeNull() expect(note1.language).toBeNull() // Create note with different partial fields const note2 = await prisma.note.create({ data: { title: 'Partial AI Note 2', content: 'Test content', userId: 'test-user-id', aiConfidence: 87, language: 'fr', languageConfidence: 0.92 // Other AI fields are null } }) expect(note2.autoGenerated).toBeNull() expect(note2.aiProvider).toBeNull() expect(note2.aiConfidence).toBe(87) expect(note2.language).toBe('fr') expect(note2.languageConfidence).toBe(0.92) }) test('should handle null values in AI fields correctly', async () => { const note = await prisma.note.create({ data: { title: 'Null AI Fields Note', content: 'Test content', userId: 'test-user-id' // All AI fields are null by default } }) // Verify all AI fields are null expect(note.autoGenerated).toBeNull() expect(note.aiProvider).toBeNull() expect(note.aiConfidence).toBeNull() expect(note.language).toBeNull() expect(note.languageConfidence).toBeNull() expect(note.lastAiAnalysis).toBeNull() }) }) describe('AiFeedback Data Migration', () => { test('should create and retrieve AiFeedback entries', async () => { const note = await prisma.note.create({ data: { title: 'Feedback Test Note', content: 'Test content', userId: 'test-user-id' } }) const feedback = await prisma.aiFeedback.create({ data: { noteId: note.id, userId: 'test-user-id', feedbackType: 'thumbs_up', feature: 'title_suggestion', originalContent: 'AI suggested title', metadata: JSON.stringify({ aiProvider: 'openai', confidence: 95, timestamp: new Date().toISOString() }) } }) // Verify feedback is correctly stored expect(feedback.noteId).toBe(note.id) expect(feedback.feedbackType).toBe('thumbs_up') expect(feedback.feature).toBe('title_suggestion') expect(feedback.originalContent).toBe('AI suggested title') expect(feedback.metadata).toBeDefined() }) test('should handle different feedback types', async () => { const note = await prisma.note.create({ data: { title: 'Feedback Types Note', content: 'Test content', userId: 'test-user-id' } }) const feedbackTypes = [ { type: 'thumbs_up', feature: 'title_suggestion', content: 'Good suggestion' }, { type: 'thumbs_down', feature: 'semantic_search', content: 'Bad result' }, { type: 'correction', feature: 'title_suggestion', content: 'Wrong', corrected: 'Correct' } ] for (const fb of feedbackTypes) { const feedback = await prisma.aiFeedback.create({ data: { noteId: note.id, userId: 'test-user-id', feedbackType: fb.type, feature: fb.feature, originalContent: fb.content, correctedContent: fb.corrected } }) expect(feedback.feedbackType).toBe(fb.type) } }) test('should store and retrieve metadata JSON correctly', async () => { const note = await prisma.note.create({ data: { title: 'Metadata Test Note', content: 'Test content', userId: 'test-user-id' } }) const metadata = { aiProvider: 'ollama', model: 'llama2-7b', confidence: 87, timestamp: new Date().toISOString(), additional: { latency: 234, tokens: 456 } } const feedback = await prisma.aiFeedback.create({ data: { noteId: note.id, userId: 'test-user-id', feedbackType: 'thumbs_up', feature: 'title_suggestion', originalContent: 'Test suggestion', metadata: JSON.stringify(metadata) } }) // Parse and verify metadata const parsedMetadata = JSON.parse(feedback.metadata || '{}') expect(parsedMetadata.aiProvider).toBe('ollama') expect(parsedMetadata.confidence).toBe(87) expect(parsedMetadata.additional).toBeDefined() }) }) describe('MemoryEchoInsight Data Migration', () => { test('should create and retrieve MemoryEchoInsight entries', async () => { const note1 = await prisma.note.create({ data: { title: 'Echo Note 1', content: 'Content about programming', userId: 'test-user-id' } }) const note2 = await prisma.note.create({ data: { title: 'Echo Note 2', content: 'Content about coding', userId: 'test-user-id' } }) const insight = await prisma.memoryEchoInsight.create({ data: { userId: 'test-user-id', note1Id: note1.id, note2Id: note2.id, similarityScore: 0.85, insight: 'These notes are similar because they both discuss programming concepts', insightDate: new Date() } }) expect(insight.note1Id).toBe(note1.id) expect(insight.note2Id).toBe(note2.id) expect(insight.similarityScore).toBe(0.85) expect(insight.insight).toBeDefined() }) test('should handle insight feedback and dismissal', async () => { const note1 = await prisma.note.create({ data: { title: 'Echo Feedback Note 1', content: 'Content A', userId: 'test-user-id' } }) const note2 = await prisma.note.create({ data: { title: 'Echo Feedback Note 2', content: 'Content B', userId: 'test-user-id' } }) const insight = await prisma.memoryEchoInsight.create({ data: { userId: 'test-user-id', note1Id: note1.id, note2Id: note2.id, similarityScore: 0.75, insight: 'Test insight', feedback: 'useful', dismissed: false } }) expect(insight.feedback).toBe('useful') expect(insight.dismissed).toBe(false) // Update insight to mark as dismissed const updatedInsight = await prisma.memoryEchoInsight.update({ where: { id: insight.id }, data: { dismissed: true } }) expect(updatedInsight.dismissed).toBe(true) }) }) describe('UserAISettings Data Migration', () => { test('should create and retrieve UserAISettings', async () => { const user = await prisma.user.create({ data: { email: 'ai-settings@test.com', name: 'AI Settings User' } }) const settings = await prisma.userAISettings.create({ data: { userId: user.id, titleSuggestions: true, semanticSearch: false, memoryEcho: true, memoryEchoFrequency: 'weekly', aiProvider: 'ollama', preferredLanguage: 'fr' } }) expect(settings.userId).toBe(user.id) expect(settings.titleSuggestions).toBe(true) expect(settings.semanticSearch).toBe(false) expect(settings.memoryEchoFrequency).toBe('weekly') expect(settings.aiProvider).toBe('ollama') expect(settings.preferredLanguage).toBe('fr') }) test('should handle default values correctly', async () => { const user = await prisma.user.create({ data: { email: 'default-ai-settings@test.com', name: 'Default AI User' } }) const settings = await prisma.userAISettings.create({ data: { userId: user.id // All other fields should use defaults } }) expect(settings.titleSuggestions).toBe(true) expect(settings.semanticSearch).toBe(true) expect(settings.memoryEcho).toBe(true) expect(settings.memoryEchoFrequency).toBe('daily') expect(settings.aiProvider).toBe('auto') expect(settings.preferredLanguage).toBe('auto') }) }) describe('Data Integrity', () => { test('should verify no data loss after migration', async () => { // Create initial data const initialNotes = await createSampleNotes(prisma, 50) const initialCount = initialNotes.length // Simulate migration by querying data const notesAfterMigration = await prisma.note.findMany() const finalCount = notesAfterMigration.length // Verify no data loss expect(finalCount).toBe(initialCount) // Verify each note's data is intact for (const note of notesAfterMigration) { expect(note.title).toBeDefined() expect(note.content).toBeDefined() } }) test('should verify no data corruption after migration', async () => { // Create notes with complex data await prisma.note.create({ data: { title: 'Complex Data Note', content: 'This is a note with **markdown** formatting', checkItems: JSON.stringify([{ text: 'Task 1', done: false }, { text: 'Task 2', done: true }]), images: JSON.stringify([{ url: 'image1.jpg', caption: 'Caption 1' }]), userId: 'test-user-id' } }) const note = await prisma.note.findFirst({ where: { title: 'Complex Data Note' } }) // Verify complex data is preserved expect(note?.content).toContain('**markdown**') if (note?.checkItems) { const checkItems = JSON.parse(note.checkItems) expect(checkItems.length).toBe(2) } if (note?.images) { const images = JSON.parse(note.images) expect(images.length).toBe(1) } }) test('should maintain foreign key relationships', async () => { // Create a user const user = await prisma.user.create({ data: { email: 'fk-test@test.com', name: 'FK Test User' } }) // Create a notebook for the user const notebook = await prisma.notebook.create({ data: { name: 'FK Test Notebook', order: 0, userId: user.id } }) // Create notes in the notebook await prisma.note.createMany({ data: [ { title: 'FK Note 1', content: 'Content 1', userId: user.id, notebookId: notebook.id }, { title: 'FK Note 2', content: 'Content 2', userId: user.id, notebookId: notebook.id } ] }) // Verify relationships are maintained const retrievedNotebook = await prisma.notebook.findUnique({ where: { id: notebook.id }, include: { notes: true } }) expect(retrievedNotebook?.notes.length).toBe(2) expect(retrievedNotebook?.userId).toBe(user.id) }) }) describe('Edge Cases', () => { test('should handle empty strings in text fields', async () => { const note = await prisma.note.create({ data: { title: '', content: 'Content with empty title', userId: 'test-user-id' } }) expect(note.title).toBe('') expect(note.content).toBe('Content with empty title') }) test('should handle very long text content', async () => { const longContent = 'A'.repeat(10000) const note = await prisma.note.create({ data: { title: 'Long Content Note', content: longContent, userId: 'test-user-id' } }) expect(note.content).toHaveLength(10000) }) test('should handle special characters in text fields', async () => { const specialChars = 'Note with émojis 🎉 and spëcial çharacters & spåcial ñumbers 123' const note = await prisma.note.create({ data: { title: specialChars, content: specialChars, userId: 'test-user-id' } }) expect(note.title).toBe(specialChars) expect(note.content).toBe(specialChars) }) test('should handle null userId in some tables (optional relationships)', async () => { const note = await prisma.note.create({ data: { title: 'No User Note', content: 'Note without userId', userId: null } }) expect(note.userId).toBeNull() }) }) describe('Migration Performance', () => { test('should complete migration within acceptable time for 100 notes', async () => { // Clean up await prisma.note.deleteMany({}) // Create 100 notes and measure time const { result, duration } = await measureExecutionTime(async () => { await createSampleNotes(prisma, 100) }) // Migration should complete quickly (< 5 seconds for 100 notes) expect(duration).toBeLessThan(5000) expect(result.length).toBe(100) }) }) describe('Batch Operations', () => { test('should handle batch insert of notes', async () => { const notesData = Array.from({ length: 20 }, (_, i) => ({ title: `Batch Note ${i + 1}`, content: `Batch content ${i + 1}`, userId: 'test-user-id', order: i })) await prisma.note.createMany({ data: notesData }) const count = await prisma.note.count() expect(count).toBe(20) }) test('should handle batch insert of feedback', async () => { const note = await prisma.note.create({ data: { title: 'Batch Feedback Note', content: 'Test content', userId: 'test-user-id' } }) const feedbackData = Array.from({ length: 10 }, (_, i) => ({ noteId: note.id, userId: 'test-user-id', feedbackType: 'thumbs_up', feature: 'title_suggestion', originalContent: `Feedback ${i + 1}` })) await prisma.aiFeedback.createMany({ data: feedbackData }) const count = await prisma.aiFeedback.count() expect(count).toBe(10) }) }) })