- 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
519 lines
16 KiB
TypeScript
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)
|
|
})
|
|
})
|
|
})
|