Keep/keep-notes/tests/migration/schema-migration.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

519 lines
16 KiB
TypeScript

/**
* Schema Migration Tests
* Validates that all schema migrations (SQL migrations) work correctly
* Tests database structure, indexes, and relationships
*/
import { PrismaClient } from '@prisma/client'
import {
setupTestEnvironment,
createTestPrismaClient,
initializeTestDatabase,
cleanupTestDatabase,
verifyTableExists,
verifyIndexExists,
verifyColumnExists,
getTableSchema
} from './setup'
describe('Schema Migration Tests', () => {
let prisma: PrismaClient
beforeAll(async () => {
await setupTestEnvironment()
prisma = createTestPrismaClient()
await initializeTestDatabase(prisma)
})
afterAll(async () => {
await cleanupTestDatabase(prisma)
})
describe('Core Table Existence', () => {
test('should have User table', async () => {
const exists = await verifyTableExists(prisma, 'User')
expect(exists).toBe(true)
})
test('should have Note table', async () => {
const exists = await verifyTableExists(prisma, 'Note')
expect(exists).toBe(true)
})
test('should have Notebook table', async () => {
const exists = await verifyTableExists(prisma, 'Notebook')
expect(exists).toBe(true)
})
test('should have Label table', async () => {
const exists = await verifyTableExists(prisma, 'Label')
expect(exists).toBe(true)
})
test('should have Account table', async () => {
const exists = await verifyTableExists(prisma, 'Account')
expect(exists).toBe(true)
})
test('should have Session table', async () => {
const exists = await verifyTableExists(prisma, 'Session')
expect(exists).toBe(true)
})
})
describe('AI Feature Tables', () => {
test('should have AiFeedback table', async () => {
const exists = await verifyTableExists(prisma, 'AiFeedback')
expect(exists).toBe(true)
})
test('should have MemoryEchoInsight table', async () => {
const exists = await verifyTableExists(prisma, 'MemoryEchoInsight')
expect(exists).toBe(true)
})
test('should have UserAISettings table', async () => {
const exists = await verifyTableExists(prisma, 'UserAISettings')
expect(exists).toBe(true)
})
})
describe('Note Table AI Fields Migration', () => {
test('should have autoGenerated column', async () => {
const exists = await verifyColumnExists(prisma, 'Note', 'autoGenerated')
expect(exists).toBe(true)
})
test('should have aiProvider column', async () => {
const exists = await verifyColumnExists(prisma, 'Note', 'aiProvider')
expect(exists).toBe(true)
})
test('should have aiConfidence column', async () => {
const exists = await verifyColumnExists(prisma, 'Note', 'aiConfidence')
expect(exists).toBe(true)
})
test('should have language column', async () => {
const exists = await verifyColumnExists(prisma, 'Note', 'language')
expect(exists).toBe(true)
})
test('should have languageConfidence column', async () => {
const exists = await verifyColumnExists(prisma, 'Note', 'languageConfidence')
expect(exists).toBe(true)
})
test('should have lastAiAnalysis column', async () => {
const exists = await verifyColumnExists(prisma, 'Note', 'lastAiAnalysis')
expect(exists).toBe(true)
})
})
describe('AiFeedback Table Structure', () => {
test('should have noteId column', async () => {
const exists = await verifyColumnExists(prisma, 'AiFeedback', 'noteId')
expect(exists).toBe(true)
})
test('should have userId column', async () => {
const exists = await verifyColumnExists(prisma, 'AiFeedback', 'userId')
expect(exists).toBe(true)
})
test('should have feedbackType column', async () => {
const exists = await verifyColumnExists(prisma, 'AiFeedback', 'feedbackType')
expect(exists).toBe(true)
})
test('should have feature column', async () => {
const exists = await verifyColumnExists(prisma, 'AiFeedback', 'feature')
expect(exists).toBe(true)
})
test('should have originalContent column', async () => {
const exists = await verifyColumnExists(prisma, 'AiFeedback', 'originalContent')
expect(exists).toBe(true)
})
test('should have correctedContent column', async () => {
const exists = await verifyColumnExists(prisma, 'AiFeedback', 'correctedContent')
expect(exists).toBe(true)
})
test('should have metadata column', async () => {
const exists = await verifyColumnExists(prisma, 'AiFeedback', 'metadata')
expect(exists).toBe(true)
})
test('should have createdAt column', async () => {
const exists = await verifyColumnExists(prisma, 'AiFeedback', 'createdAt')
expect(exists).toBe(true)
})
})
describe('MemoryEchoInsight Table Structure', () => {
test('should have userId column', async () => {
const exists = await verifyColumnExists(prisma, 'MemoryEchoInsight', 'userId')
expect(exists).toBe(true)
})
test('should have note1Id column', async () => {
const exists = await verifyColumnExists(prisma, 'MemoryEchoInsight', 'note1Id')
expect(exists).toBe(true)
})
test('should have note2Id column', async () => {
const exists = await verifyColumnExists(prisma, 'MemoryEchoInsight', 'note2Id')
expect(exists).toBe(true)
})
test('should have similarityScore column', async () => {
const exists = await verifyColumnExists(prisma, 'MemoryEchoInsight', 'similarityScore')
expect(exists).toBe(true)
})
test('should have insight column', async () => {
const exists = await verifyColumnExists(prisma, 'MemoryEchoInsight', 'insight')
expect(exists).toBe(true)
})
test('should have insightDate column', async () => {
const exists = await verifyColumnExists(prisma, 'MemoryEchoInsight', 'insightDate')
expect(exists).toBe(true)
})
test('should have viewed column', async () => {
const exists = await verifyColumnExists(prisma, 'MemoryEchoInsight', 'viewed')
expect(exists).toBe(true)
})
test('should have feedback column', async () => {
const exists = await verifyColumnExists(prisma, 'MemoryEchoInsight', 'feedback')
expect(exists).toBe(true)
})
test('should have dismissed column', async () => {
const exists = await verifyColumnExists(prisma, 'MemoryEchoInsight', 'dismissed')
expect(exists).toBe(true)
})
})
describe('UserAISettings Table Structure', () => {
test('should have userId column', async () => {
const exists = await verifyColumnExists(prisma, 'UserAISettings', 'userId')
expect(exists).toBe(true)
})
test('should have titleSuggestions column', async () => {
const exists = await verifyColumnExists(prisma, 'UserAISettings', 'titleSuggestions')
expect(exists).toBe(true)
})
test('should have semanticSearch column', async () => {
const exists = await verifyColumnExists(prisma, 'UserAISettings', 'semanticSearch')
expect(exists).toBe(true)
})
test('should have paragraphRefactor column', async () => {
const exists = await verifyColumnExists(prisma, 'UserAISettings', 'paragraphRefactor')
expect(exists).toBe(true)
})
test('should have memoryEcho column', async () => {
const exists = await verifyColumnExists(prisma, 'UserAISettings', 'memoryEcho')
expect(exists).toBe(true)
})
test('should have memoryEchoFrequency column', async () => {
const exists = await verifyColumnExists(prisma, 'UserAISettings', 'memoryEchoFrequency')
expect(exists).toBe(true)
})
test('should have aiProvider column', async () => {
const exists = await verifyColumnExists(prisma, 'UserAISettings', 'aiProvider')
expect(exists).toBe(true)
})
test('should have preferredLanguage column', async () => {
const exists = await verifyColumnExists(prisma, 'UserAISettings', 'preferredLanguage')
expect(exists).toBe(true)
})
test('should have fontSize column', async () => {
const exists = await verifyColumnExists(prisma, 'UserAISettings', 'fontSize')
expect(exists).toBe(true)
})
test('should have demoMode column', async () => {
const exists = await verifyColumnExists(prisma, 'UserAISettings', 'demoMode')
expect(exists).toBe(true)
})
test('should have showRecentNotes column', async () => {
const exists = await verifyColumnExists(prisma, 'UserAISettings', 'showRecentNotes')
expect(exists).toBe(true)
})
test('should have emailNotifications column', async () => {
const exists = await verifyColumnExists(prisma, 'UserAISettings', 'emailNotifications')
expect(exists).toBe(true)
})
test('should have desktopNotifications column', async () => {
const exists = await verifyColumnExists(prisma, 'UserAISettings', 'desktopNotifications')
expect(exists).toBe(true)
})
test('should have anonymousAnalytics column', async () => {
const exists = await verifyColumnExists(prisma, 'UserAISettings', 'anonymousAnalytics')
expect(exists).toBe(true)
})
})
describe('Index Creation', () => {
test('should have indexes on AiFeedback.noteId', async () => {
const exists = await verifyIndexExists(prisma, 'AiFeedback', 'AiFeedback_noteId_idx')
expect(exists).toBe(true)
})
test('should have indexes on AiFeedback.userId', async () => {
const exists = await verifyIndexExists(prisma, 'AiFeedback', 'AiFeedback_userId_idx')
expect(exists).toBe(true)
})
test('should have indexes on AiFeedback.feature', async () => {
const exists = await verifyIndexExists(prisma, 'AiFeedback', 'AiFeedback_feature_idx')
expect(exists).toBe(true)
})
test('should have indexes on AiFeedback.createdAt', async () => {
const exists = await verifyIndexExists(prisma, 'AiFeedback', 'AiFeedback_createdAt_idx')
expect(exists).toBe(true)
})
test('should have indexes on Note table', async () => {
// Note table should have indexes on various columns
const schema = await getTableSchema(prisma, 'sqlite_master')
expect(schema).toBeDefined()
})
})
describe('Foreign Key Relationships', () => {
test('should maintain Note to AiFeedback relationship', async () => {
// Create a test note
const note = await prisma.note.create({
data: {
title: 'Test FK Note',
content: 'Test content',
userId: 'test-user-id'
}
})
// Create feedback linked to the note
const feedback = await prisma.aiFeedback.create({
data: {
noteId: note.id,
userId: 'test-user-id',
feedbackType: 'thumbs_up',
feature: 'title_suggestion',
originalContent: 'Test feedback'
}
})
expect(feedback.noteId).toBe(note.id)
})
test('should maintain User to AiFeedback relationship', async () => {
// Create a test note
const note = await prisma.note.create({
data: {
title: 'Test User FK Note',
content: 'Test content',
userId: 'test-user-id'
}
})
// Create feedback linked to user
const feedback = await prisma.aiFeedback.create({
data: {
noteId: note.id,
userId: 'test-user-id',
feedbackType: 'thumbs_down',
feature: 'semantic_search',
originalContent: 'Test feedback'
}
})
expect(feedback.userId).toBe('test-user-id')
})
test('should cascade delete AiFeedback when Note is deleted', async () => {
// Create a note with feedback
const note = await prisma.note.create({
data: {
title: 'Test Cascade Note',
content: 'Test content',
userId: 'test-user-id'
}
})
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 the 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)
})
test('should maintain Note to Notebook relationship', async () => {
// Create a notebook
const notebook = await prisma.notebook.create({
data: {
name: 'Test Notebook',
order: 0,
userId: 'test-user-id'
}
})
// Create a note in the notebook
const note = await prisma.note.create({
data: {
title: 'Test Notebook Note',
content: 'Test content',
userId: 'test-user-id',
notebookId: notebook.id
}
})
expect(note.notebookId).toBe(notebook.id)
})
})
describe('Unique Constraints', () => {
test('should enforce unique constraint on User.email', async () => {
// First user should be created
await prisma.user.create({
data: {
email: 'unique@test.com',
name: 'Unique User'
}
})
// Second user with same email should fail
await expect(
prisma.user.create({
data: {
email: 'unique@test.com',
name: 'Duplicate User'
}
})
).rejects.toThrow()
})
test('should enforce unique constraint on Notebook userId+name', async () => {
const userId = 'test-user-unique'
// First notebook should be created
await prisma.notebook.create({
data: {
name: 'Unique Notebook',
order: 0,
userId
}
})
// Second notebook with same name for same user should fail
await expect(
prisma.notebook.create({
data: {
name: 'Unique Notebook',
order: 1,
userId
}
})
).rejects.toThrow()
})
})
describe('Default Values', () => {
test('should have default values for Note table', async () => {
const note = await prisma.note.create({
data: {
content: 'Test content',
userId: 'test-user-id'
}
})
expect(note.color).toBe('default')
expect(note.isPinned).toBe(false)
expect(note.isArchived).toBe(false)
expect(note.type).toBe('text')
expect(note.size).toBe('small')
expect(note.order).toBe(0)
})
test('should have default values for UserAISettings', async () => {
const user = await prisma.user.create({
data: {
email: 'default-settings@test.com'
}
})
const settings = await prisma.userAISettings.create({
data: {
userId: user.id
}
})
expect(settings.titleSuggestions).toBe(true)
expect(settings.semanticSearch).toBe(true)
expect(settings.paragraphRefactor).toBe(true)
expect(settings.memoryEcho).toBe(true)
expect(settings.memoryEchoFrequency).toBe('daily')
expect(settings.aiProvider).toBe('auto')
expect(settings.preferredLanguage).toBe('auto')
expect(settings.fontSize).toBe('medium')
expect(settings.demoMode).toBe(false)
expect(settings.showRecentNotes).toBe(false)
expect(settings.emailNotifications).toBe(false)
expect(settings.desktopNotifications).toBe(false)
expect(settings.anonymousAnalytics).toBe(false)
})
})
describe('Schema Version Tracking', () => {
test('should have all migrations applied', async () => {
// Check that the migration tables exist
const migrationsExist = await verifyTableExists(prisma, '_prisma_migrations')
// In SQLite with Prisma, migrations are tracked via _prisma_migrations table
// For this test, we just verify the schema is complete
const hasUser = await verifyTableExists(prisma, 'User')
const hasNote = await verifyTableExists(prisma, 'Note')
const hasAiFeedback = await verifyTableExists(prisma, 'AiFeedback')
expect(hasUser && hasNote && hasAiFeedback).toBe(true)
})
})
})