Keep/keep-notes/tests/migration/integrity.test.ts
sepehr ddb67ba9e5 fix: unify theme system - fix theme switching persistence
- Unified localStorage key to 'theme-preference' across all components
- Fixed header.tsx using wrong localStorage key ('theme' instead of 'theme-preference')
- Added localStorage hybrid persistence for instant theme changes
- Removed router.refresh() which was causing stale data revert
- Replaced Blue theme with Sepia
- Consolidated auth() calls to prevent race conditions
- Updated UserSettingsData types to include all themes
2026-01-18 22:33:41 +01:00

722 lines
22 KiB
TypeScript

/**
* Data Integrity Tests
* Validates that data is preserved and not corrupted during migration
* Tests data loss prevention, foreign key relationships, and indexes
*/
import { PrismaClient } from '@prisma/client'
import {
setupTestEnvironment,
createTestPrismaClient,
initializeTestDatabase,
cleanupTestDatabase,
createSampleNotes,
verifyDataIntegrity
} from './setup'
describe('Data Integrity Tests', () => {
let prisma: PrismaClient
beforeAll(async () => {
await setupTestEnvironment()
prisma = createTestPrismaClient()
await initializeTestDatabase(prisma)
})
afterAll(async () => {
await cleanupTestDatabase(prisma)
})
describe('No Data Loss', () => {
test('should preserve all notes after migration', async () => {
// Create test notes
const initialNotes = await createSampleNotes(prisma, 50)
const initialCount = initialNotes.length
// Query after migration
const notesAfterMigration = await prisma.note.findMany()
const finalCount = notesAfterMigration.length
// Verify no data loss
expect(finalCount).toBe(initialCount)
})
test('should preserve note titles', async () => {
const testTitles = [
'Important Meeting Notes',
'Shopping List',
'Project Ideas',
'Recipe Collection',
'Book Reviews'
]
// Create notes with specific titles
for (const title of testTitles) {
await prisma.note.create({
data: {
title,
content: `Content for ${title}`,
userId: 'test-user-id'
}
})
}
// Verify all titles are preserved
const notes = await prisma.note.findMany({
where: { title: { in: testTitles } }
})
const preservedTitles = notes.map(n => n.title)
for (const title of testTitles) {
expect(preservedTitles).toContain(title)
}
})
test('should preserve note content', async () => {
const testContent = [
'This is a simple text note',
'Note with **markdown** formatting',
'Note with [links](https://example.com)',
'Note with numbers: 1, 2, 3, 4, 5',
'Note with special characters: émojis 🎉 & çharacters'
]
// Create notes with specific content
for (const content of testContent) {
await prisma.note.create({
data: {
title: `Content Test`,
content,
userId: 'test-user-id'
}
})
}
// Verify all content is preserved
const notes = await prisma.note.findMany({
where: { title: 'Content Test' }
})
const preservedContent = notes.map(n => n.content)
for (const content of testContent) {
expect(preservedContent).toContain(content)
}
})
test('should preserve note metadata', async () => {
// Create note with all metadata
const note = await prisma.note.create({
data: {
title: 'Metadata Test Note',
content: 'Test content',
color: 'red',
isPinned: true,
isArchived: false,
type: 'text',
size: 'large',
userId: 'test-user-id',
order: 5
}
})
// Verify metadata is preserved
const retrieved = await prisma.note.findUnique({
where: { id: note.id }
})
expect(retrieved?.color).toBe('red')
expect(retrieved?.isPinned).toBe(true)
expect(retrieved?.isArchived).toBe(false)
expect(retrieved?.type).toBe('text')
expect(retrieved?.size).toBe('large')
expect(retrieved?.order).toBe(5)
})
})
describe('No Data Corruption', () => {
test('should preserve checkItems JSON structure', async () => {
const checkItems = JSON.stringify([
{ text: 'Buy groceries', done: false },
{ text: 'Call dentist', done: true },
{ text: 'Finish report', done: false },
{ text: 'Schedule meeting', done: false }
])
const note = await prisma.note.create({
data: {
title: 'Checklist Test Note',
content: 'My checklist',
checkItems,
userId: 'test-user-id'
}
})
// Verify checkItems are preserved and can be parsed
const retrieved = await prisma.note.findUnique({
where: { id: note.id }
})
expect(retrieved?.checkItems).toBeDefined()
const parsedCheckItems = JSON.parse(retrieved?.checkItems || '[]')
expect(parsedCheckItems.length).toBe(4)
expect(parsedCheckItems[0].text).toBe('Buy groceries')
expect(parsedCheckItems[0].done).toBe(false)
expect(parsedCheckItems[1].done).toBe(true)
})
test('should preserve images JSON structure', async () => {
const images = JSON.stringify([
{ url: 'image1.jpg', caption: 'First image' },
{ url: 'image2.jpg', caption: 'Second image' },
{ url: 'image3.jpg', caption: 'Third image' }
])
const note = await prisma.note.create({
data: {
title: 'Images Test Note',
content: 'Note with images',
images,
userId: 'test-user-id'
}
})
// Verify images are preserved and can be parsed
const retrieved = await prisma.note.findUnique({
where: { id: note.id }
})
expect(retrieved?.images).toBeDefined()
const parsedImages = JSON.parse(retrieved?.images || '[]')
expect(parsedImages.length).toBe(3)
expect(parsedImages[0].url).toBe('image1.jpg')
expect(parsedImages[0].caption).toBe('First image')
})
test('should preserve labels JSON structure', async () => {
const labels = JSON.stringify(['work', 'important', 'project'])
const note = await prisma.note.create({
data: {
title: 'Labels Test Note',
content: 'Note with labels',
labels,
userId: 'test-user-id'
}
})
// Verify labels are preserved and can be parsed
const retrieved = await prisma.note.findUnique({
where: { id: note.id }
})
expect(retrieved?.labels).toBeDefined()
const parsedLabels = JSON.parse(retrieved?.labels || '[]')
expect(parsedLabels.length).toBe(3)
expect(parsedLabels).toContain('work')
expect(parsedLabels).toContain('important')
expect(parsedLabels).toContain('project')
})
test('should preserve embedding JSON structure', async () => {
const embedding = JSON.stringify({
vector: [0.1, 0.2, 0.3, 0.4, 0.5],
model: 'text-embedding-ada-002',
timestamp: new Date().toISOString()
})
const note = await prisma.note.create({
data: {
title: 'Embedding Test Note',
content: 'Note with embedding',
embedding,
userId: 'test-user-id'
}
})
// Verify embedding is preserved and can be parsed
const retrieved = await prisma.note.findUnique({
where: { id: note.id }
})
expect(retrieved?.embedding).toBeDefined()
const parsedEmbedding = JSON.parse(retrieved?.embedding || '{}')
expect(parsedEmbedding.vector).toEqual([0.1, 0.2, 0.3, 0.4, 0.5])
expect(parsedEmbedding.model).toBe('text-embedding-ada-002')
})
test('should preserve links JSON structure', async () => {
const links = JSON.stringify([
{ url: 'https://example.com', title: 'Example' },
{ url: 'https://docs.example.com', title: 'Documentation' }
])
const note = await prisma.note.create({
data: {
title: 'Links Test Note',
content: 'Note with links',
links,
userId: 'test-user-id'
}
})
// Verify links are preserved and can be parsed
const retrieved = await prisma.note.findUnique({
where: { id: note.id }
})
expect(retrieved?.links).toBeDefined()
const parsedLinks = JSON.parse(retrieved?.links || '[]')
expect(parsedLinks.length).toBe(2)
expect(parsedLinks[0].url).toBe('https://example.com')
expect(parsedLinks[0].title).toBe('Example')
})
})
describe('Foreign Key Relationships', () => {
test('should maintain Note to User relationship', async () => {
const user = await prisma.user.create({
data: {
email: 'fk-integrity@test.com',
name: 'FK Integrity User'
}
})
const note = await prisma.note.create({
data: {
title: 'User Integrity Note',
content: 'Test content',
userId: user.id
}
})
// Verify relationship is maintained
expect(note.userId).toBe(user.id)
// Query user's notes
const userNotes = await prisma.note.findMany({
where: { userId: user.id }
})
expect(userNotes.length).toBeGreaterThan(0)
expect(userNotes.some(n => n.id === note.id)).toBe(true)
})
test('should maintain Note to Notebook relationship', async () => {
const notebook = await prisma.notebook.create({
data: {
name: 'Integrity Notebook',
order: 0,
userId: 'test-user-id'
}
})
const note = await prisma.note.create({
data: {
title: 'Notebook Integrity Note',
content: 'Test content',
userId: 'test-user-id',
notebookId: notebook.id
}
})
// Verify relationship is maintained
expect(note.notebookId).toBe(notebook.id)
// Query notebook's notes
const notebookNotes = await prisma.note.findMany({
where: { notebookId: notebook.id }
})
expect(notebookNotes.length).toBeGreaterThan(0)
expect(notebookNotes.some(n => n.id === note.id)).toBe(true)
})
test('should maintain AiFeedback to Note relationship', async () => {
const note = await prisma.note.create({
data: {
title: 'Feedback Integrity 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: 'Test feedback'
}
})
// Verify relationship is maintained
expect(feedback.noteId).toBe(note.id)
// Query note's feedback
const noteFeedback = await prisma.aiFeedback.findMany({
where: { noteId: note.id }
})
expect(noteFeedback.length).toBeGreaterThan(0)
expect(noteFeedback.some(f => f.id === feedback.id)).toBe(true)
})
test('should maintain AiFeedback to User relationship', async () => {
const user = await prisma.user.create({
data: {
email: 'feedback-user@test.com',
name: 'Feedback User'
}
})
const note = await prisma.note.create({
data: {
title: 'User Feedback Note',
content: 'Test content',
userId: user.id
}
})
const feedback = await prisma.aiFeedback.create({
data: {
noteId: note.id,
userId: user.id,
feedbackType: 'thumbs_up',
feature: 'title_suggestion',
originalContent: 'Test feedback'
}
})
// Verify relationship is maintained
expect(feedback.userId).toBe(user.id)
// Query user's feedback
const userFeedback = await prisma.aiFeedback.findMany({
where: { userId: user.id }
})
expect(userFeedback.length).toBeGreaterThan(0)
expect(userFeedback.some(f => f.id === feedback.id)).toBe(true)
})
test('should maintain cascade delete correctly', async () => {
// Create a note with feedback
const note = await prisma.note.create({
data: {
title: 'Cascade Delete 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: 'Test feedback'
}
})
// Verify feedback exists
const feedbacksBefore = await prisma.aiFeedback.findMany({
where: { noteId: note.id }
})
expect(feedbacksBefore.length).toBe(1)
// Delete note
await prisma.note.delete({
where: { id: note.id }
})
// Verify feedback is cascade deleted
const feedbacksAfter = await prisma.aiFeedback.findMany({
where: { noteId: note.id }
})
expect(feedbacksAfter.length).toBe(0)
})
})
describe('Index Integrity', () => {
test('should maintain index on Note.isPinned', async () => {
// Create notes with various pinned states
await prisma.note.createMany({
data: [
{ title: 'Pinned 1', content: 'Content 1', userId: 'test-user-id', isPinned: true },
{ title: 'Not Pinned 1', content: 'Content 2', userId: 'test-user-id', isPinned: false },
{ title: 'Pinned 2', content: 'Content 3', userId: 'test-user-id', isPinned: true },
{ title: 'Not Pinned 2', content: 'Content 4', userId: 'test-user-id', isPinned: false }
]
})
// Query by isPinned (should use index)
const pinnedNotes = await prisma.note.findMany({
where: { isPinned: true }
})
expect(pinnedNotes.length).toBe(2)
expect(pinnedNotes.every(n => n.isPinned === true)).toBe(true)
})
test('should maintain index on Note.order', async () => {
// Create notes with specific order
await prisma.note.createMany({
data: [
{ title: 'Order 0', content: 'Content 0', userId: 'test-user-id', order: 0 },
{ title: 'Order 1', content: 'Content 1', userId: 'test-user-id', order: 1 },
{ title: 'Order 2', content: 'Content 2', userId: 'test-user-id', order: 2 },
{ title: 'Order 3', content: 'Content 3', userId: 'test-user-id', order: 3 }
]
})
// Query ordered by order (should use index)
const orderedNotes = await prisma.note.findMany({
orderBy: { order: 'asc' }
})
expect(orderedNotes[0].order).toBe(0)
expect(orderedNotes[1].order).toBe(1)
expect(orderedNotes[2].order).toBe(2)
expect(orderedNotes[3].order).toBe(3)
})
test('should maintain index on AiFeedback.noteId', async () => {
const note = await prisma.note.create({
data: {
title: 'Index Feedback Note',
content: 'Test content',
userId: 'test-user-id'
}
})
// Create multiple feedback entries
await prisma.aiFeedback.createMany({
data: [
{ noteId: note.id, userId: 'test-user-id', feedbackType: 'thumbs_up', feature: 'title_suggestion', originalContent: 'Feedback 1' },
{ noteId: note.id, userId: 'test-user-id', feedbackType: 'thumbs_down', feature: 'semantic_search', originalContent: 'Feedback 2' },
{ noteId: note.id, userId: 'test-user-id', feedbackType: 'thumbs_up', feature: 'paragraph_refactor', originalContent: 'Feedback 3' }
]
})
// Query by noteId (should use index)
const feedbacks = await prisma.aiFeedback.findMany({
where: { noteId: note.id }
})
expect(feedbacks.length).toBe(3)
})
test('should maintain index on AiFeedback.userId', async () => {
const note = await prisma.note.create({
data: {
title: 'User Index Feedback Note',
content: 'Test content',
userId: 'test-user-id'
}
})
// Create multiple feedback entries for same user
await prisma.aiFeedback.createMany({
data: [
{ noteId: note.id, userId: 'test-user-id', feedbackType: 'thumbs_up', feature: 'title_suggestion', originalContent: 'Feedback 1' },
{ noteId: note.id, userId: 'test-user-id', feedbackType: 'thumbs_down', feature: 'semantic_search', originalContent: 'Feedback 2' }
]
})
// Query by userId (should use index)
const userFeedbacks = await prisma.aiFeedback.findMany({
where: { userId: 'test-user-id' }
})
expect(userFeedbacks.length).toBeGreaterThanOrEqual(2)
})
test('should maintain index on AiFeedback.feature', async () => {
const note1 = await prisma.note.create({
data: {
title: 'Feature Index Note 1',
content: 'Test content 1',
userId: 'test-user-id'
}
})
const note2 = await prisma.note.create({
data: {
title: 'Feature Index Note 2',
content: 'Test content 2',
userId: 'test-user-id'
}
})
// Create feedback with same feature
await prisma.aiFeedback.createMany({
data: [
{ noteId: note1.id, userId: 'test-user-id', feedbackType: 'thumbs_up', feature: 'title_suggestion', originalContent: 'Feedback 1' },
{ noteId: note2.id, userId: 'test-user-id', feedbackType: 'thumbs_up', feature: 'title_suggestion', originalContent: 'Feedback 2' }
]
})
// Query by feature (should use index)
const titleFeedbacks = await prisma.aiFeedback.findMany({
where: { feature: 'title_suggestion' }
})
expect(titleFeedbacks.length).toBeGreaterThanOrEqual(2)
})
})
describe('AI Fields Integrity', () => {
test('should preserve AI fields correctly', async () => {
const note = await prisma.note.create({
data: {
title: 'AI Fields Integrity Note',
content: 'Test content',
userId: 'test-user-id',
autoGenerated: true,
aiProvider: 'openai',
aiConfidence: 95,
language: 'en',
languageConfidence: 0.98,
lastAiAnalysis: new Date()
}
})
// Verify all AI fields are preserved
const retrieved = await prisma.note.findUnique({
where: { id: note.id }
})
expect(retrieved?.autoGenerated).toBe(true)
expect(retrieved?.aiProvider).toBe('openai')
expect(retrieved?.aiConfidence).toBe(95)
expect(retrieved?.language).toBe('en')
expect(retrieved?.languageConfidence).toBeCloseTo(0.98)
expect(retrieved?.lastAiAnalysis).toBeDefined()
})
test('should preserve null 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
const retrieved = await prisma.note.findUnique({
where: { id: note.id }
})
expect(retrieved?.autoGenerated).toBeNull()
expect(retrieved?.aiProvider).toBeNull()
expect(retrieved?.aiConfidence).toBeNull()
expect(retrieved?.language).toBeNull()
expect(retrieved?.languageConfidence).toBeNull()
expect(retrieved?.lastAiAnalysis).toBeNull()
})
})
describe('Batch Operations Integrity', () => {
test('should preserve data integrity during batch insert', async () => {
const notesData = Array.from({ length: 50 }, (_, i) => ({
title: `Batch Integrity Note ${i}`,
content: `Content ${i}`,
userId: 'test-user-id',
order: i,
isPinned: i % 2 === 0
}))
const result = await prisma.note.createMany({
data: notesData
})
expect(result.count).toBe(50)
// Verify all notes are created correctly
const notes = await prisma.note.findMany({
where: { title: { contains: 'Batch Integrity Note' } }
})
expect(notes.length).toBe(50)
// Verify data integrity
for (const note of notes) {
expect(note.content).toBeDefined()
expect(note.userId).toBe('test-user-id')
}
})
})
describe('Data Type Integrity', () => {
test('should preserve boolean values correctly', async () => {
const note = await prisma.note.create({
data: {
title: 'Boolean Test Note',
content: 'Test content',
userId: 'test-user-id',
isPinned: true,
isArchived: false
}
})
const retrieved = await prisma.note.findUnique({
where: { id: note.id }
})
expect(retrieved?.isPinned).toBe(true)
expect(retrieved?.isArchived).toBe(false)
})
test('should preserve numeric values correctly', async () => {
const note = await prisma.note.create({
data: {
title: 'Numeric Test Note',
content: 'Test content',
userId: 'test-user-id',
order: 42,
aiConfidence: 87,
languageConfidence: 0.95
}
})
const retrieved = await prisma.note.findUnique({
where: { id: note.id }
})
expect(retrieved?.order).toBe(42)
expect(retrieved?.aiConfidence).toBe(87)
expect(retrieved?.languageConfidence).toBeCloseTo(0.95)
})
test('should preserve date values correctly', async () => {
const testDate = new Date('2024-01-15T10:30:00Z')
const note = await prisma.note.create({
data: {
title: 'Date Test Note',
content: 'Test content',
userId: 'test-user-id',
reminder: testDate,
lastAiAnalysis: testDate
}
})
const retrieved = await prisma.note.findUnique({
where: { id: note.id }
})
expect(retrieved?.reminder).toBeDefined()
expect(retrieved?.lastAiAnalysis).toBeDefined()
})
})
})