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
This commit is contained in:
518
keep-notes/tests/migration/schema-migration.test.ts
Normal file
518
keep-notes/tests/migration/schema-migration.test.ts
Normal file
@@ -0,0 +1,518 @@
|
||||
/**
|
||||
* 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)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user