- 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
449 lines
13 KiB
TypeScript
449 lines
13 KiB
TypeScript
/**
|
|
* Test suite for AI field migrations
|
|
* Validates that Note and AiFeedback models work correctly with new AI fields
|
|
*/
|
|
|
|
import { PrismaClient } from '@prisma/client'
|
|
|
|
const prisma = new PrismaClient()
|
|
|
|
describe('AI Fields Migration Tests', () => {
|
|
beforeAll(async () => {
|
|
// Ensure clean test environment
|
|
await prisma.aiFeedback.deleteMany({})
|
|
await prisma.note.deleteMany({
|
|
where: { title: { contains: 'TEST_AI' } }
|
|
})
|
|
})
|
|
|
|
afterAll(async () => {
|
|
// Cleanup
|
|
await prisma.aiFeedback.deleteMany({})
|
|
await prisma.note.deleteMany({
|
|
where: { title: { contains: 'TEST_AI' } }
|
|
})
|
|
await prisma.$disconnect()
|
|
})
|
|
|
|
describe('Note Model - AI Fields', () => {
|
|
test('should create note without AI fields (backward compatibility)', async () => {
|
|
const note = await prisma.note.create({
|
|
data: {
|
|
title: 'TEST_AI: Note without AI',
|
|
content: 'This is a test note without AI fields',
|
|
userId: 'test-user-id'
|
|
}
|
|
})
|
|
|
|
expect(note).toBeDefined()
|
|
expect(note.id).toBeDefined()
|
|
expect(note.title).toBe('TEST_AI: Note without AI')
|
|
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 create note with all AI fields populated', async () => {
|
|
const testDate = new Date()
|
|
const note = await prisma.note.create({
|
|
data: {
|
|
title: 'TEST_AI: Note with AI fields',
|
|
content: 'This is a test note with AI fields',
|
|
userId: 'test-user-id',
|
|
autoGenerated: true,
|
|
aiProvider: 'openai',
|
|
aiConfidence: 95,
|
|
language: 'fr',
|
|
languageConfidence: 0.98,
|
|
lastAiAnalysis: testDate
|
|
}
|
|
})
|
|
|
|
expect(note).toBeDefined()
|
|
expect(note.autoGenerated).toBe(true)
|
|
expect(note.aiProvider).toBe('openai')
|
|
expect(note.aiConfidence).toBe(95)
|
|
expect(note.language).toBe('fr')
|
|
expect(note.languageConfidence).toBe(0.98)
|
|
expect(note.lastAiAnalysis).toEqual(testDate)
|
|
})
|
|
|
|
test('should update note with AI fields', async () => {
|
|
const note = await prisma.note.create({
|
|
data: {
|
|
title: 'TEST_AI: Note for update test',
|
|
content: 'Initial content',
|
|
userId: 'test-user-id'
|
|
}
|
|
})
|
|
|
|
const updatedNote = await prisma.note.update({
|
|
where: { id: note.id },
|
|
data: {
|
|
autoGenerated: true,
|
|
aiProvider: 'ollama',
|
|
aiConfidence: 87
|
|
}
|
|
})
|
|
|
|
expect(updatedNote.autoGenerated).toBe(true)
|
|
expect(updatedNote.aiProvider).toBe('ollama')
|
|
expect(updatedNote.aiConfidence).toBe(87)
|
|
})
|
|
|
|
test('should query notes filtered by AI fields', async () => {
|
|
await prisma.note.create({
|
|
data: {
|
|
title: 'TEST_AI: Auto-generated note 1',
|
|
content: 'Test content',
|
|
userId: 'test-user-id',
|
|
autoGenerated: true,
|
|
aiProvider: 'openai'
|
|
}
|
|
})
|
|
|
|
await prisma.note.create({
|
|
data: {
|
|
title: 'TEST_AI: Auto-generated note 2',
|
|
content: 'Test content',
|
|
userId: 'test-user-id',
|
|
autoGenerated: true,
|
|
aiProvider: 'ollama'
|
|
}
|
|
})
|
|
|
|
const autoGeneratedNotes = await prisma.note.findMany({
|
|
where: {
|
|
title: { contains: 'TEST_AI' },
|
|
autoGenerated: true
|
|
}
|
|
})
|
|
|
|
expect(autoGeneratedNotes.length).toBeGreaterThanOrEqual(2)
|
|
expect(autoGeneratedNotes.every(n => n.autoGenerated === true)).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('AiFeedback Model', () => {
|
|
test('should create feedback entry', async () => {
|
|
const note = await prisma.note.create({
|
|
data: {
|
|
title: 'TEST_AI: Note for feedback',
|
|
content: 'Test note',
|
|
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,
|
|
model: 'gpt-4',
|
|
timestamp: new Date().toISOString()
|
|
})
|
|
}
|
|
})
|
|
|
|
expect(feedback).toBeDefined()
|
|
expect(feedback.id).toBeDefined()
|
|
expect(feedback.feedbackType).toBe('thumbs_up')
|
|
expect(feedback.feature).toBe('title_suggestion')
|
|
expect(feedback.originalContent).toBe('AI suggested title')
|
|
expect(feedback.noteId).toBe(note.id)
|
|
})
|
|
|
|
test('should handle thumbs_down feedback', async () => {
|
|
const note = await prisma.note.create({
|
|
data: {
|
|
title: 'TEST_AI: Note for thumbs down',
|
|
content: 'Test note',
|
|
userId: 'test-user-id'
|
|
}
|
|
})
|
|
|
|
const feedback = await prisma.aiFeedback.create({
|
|
data: {
|
|
noteId: note.id,
|
|
userId: 'test-user-id',
|
|
feedbackType: 'thumbs_down',
|
|
feature: 'title_suggestion',
|
|
originalContent: 'Bad suggestion'
|
|
}
|
|
})
|
|
|
|
expect(feedback.feedbackType).toBe('thumbs_down')
|
|
})
|
|
|
|
test('should handle correction feedback', async () => {
|
|
const note = await prisma.note.create({
|
|
data: {
|
|
title: 'TEST_AI: Note for correction',
|
|
content: 'Test note',
|
|
userId: 'test-user-id'
|
|
}
|
|
})
|
|
|
|
const feedback = await prisma.aiFeedback.create({
|
|
data: {
|
|
noteId: note.id,
|
|
userId: 'test-user-id',
|
|
feedbackType: 'correction',
|
|
feature: 'title_suggestion',
|
|
originalContent: 'Wrong suggestion',
|
|
correctedContent: 'Corrected version'
|
|
}
|
|
})
|
|
|
|
expect(feedback.feedbackType).toBe('correction')
|
|
expect(feedback.correctedContent).toBe('Corrected version')
|
|
})
|
|
|
|
test('should query feedback by note', async () => {
|
|
const note = await prisma.note.create({
|
|
data: {
|
|
title: 'TEST_AI: Note for feedback query',
|
|
content: 'Test note',
|
|
userId: 'test-user-id'
|
|
}
|
|
})
|
|
|
|
await prisma.aiFeedback.create({
|
|
data: {
|
|
noteId: note.id,
|
|
userId: 'test-user-id',
|
|
feedbackType: 'thumbs_up',
|
|
feature: 'title_suggestion',
|
|
originalContent: 'Feedback 1'
|
|
}
|
|
})
|
|
|
|
await prisma.aiFeedback.create({
|
|
data: {
|
|
noteId: note.id,
|
|
userId: 'test-user-id',
|
|
feedbackType: 'thumbs_down',
|
|
feature: 'title_suggestion',
|
|
originalContent: 'Feedback 2'
|
|
}
|
|
})
|
|
|
|
const feedbacks = await prisma.aiFeedback.findMany({
|
|
where: { noteId: note.id },
|
|
orderBy: { createdAt: 'asc' }
|
|
})
|
|
|
|
expect(feedbacks.length).toBe(2)
|
|
})
|
|
|
|
test('should query feedback by feature', async () => {
|
|
const note = await prisma.note.create({
|
|
data: {
|
|
title: 'TEST_AI: Note for feature query',
|
|
content: 'Test note',
|
|
userId: 'test-user-id'
|
|
}
|
|
})
|
|
|
|
await prisma.aiFeedback.create({
|
|
data: {
|
|
noteId: note.id,
|
|
userId: 'test-user-id',
|
|
feedbackType: 'thumbs_up',
|
|
feature: 'title_suggestion',
|
|
originalContent: 'Feedback 1'
|
|
}
|
|
})
|
|
|
|
await prisma.aiFeedback.create({
|
|
data: {
|
|
noteId: note.id,
|
|
userId: 'test-user-id',
|
|
feedbackType: 'thumbs_up',
|
|
feature: 'semantic_search',
|
|
originalContent: 'Feedback 2'
|
|
}
|
|
})
|
|
|
|
const titleFeedbacks = await prisma.aiFeedback.findMany({
|
|
where: { feature: 'title_suggestion' }
|
|
})
|
|
|
|
expect(titleFeedbacks.length).toBeGreaterThanOrEqual(1)
|
|
expect(titleFeedbacks.every(f => f.feature === 'title_suggestion')).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('Cascade Deletion', () => {
|
|
test('should cascade delete feedback when note is deleted', async () => {
|
|
const note = await prisma.note.create({
|
|
data: {
|
|
title: 'TEST_AI: Note for cascade test',
|
|
content: 'Test note',
|
|
userId: 'test-user-id'
|
|
}
|
|
})
|
|
|
|
await prisma.aiFeedback.create({
|
|
data: {
|
|
noteId: note.id,
|
|
userId: 'test-user-id',
|
|
feedbackType: 'thumbs_up',
|
|
feature: 'title_suggestion',
|
|
originalContent: 'Feedback to be deleted'
|
|
}
|
|
})
|
|
|
|
// 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 was cascade deleted
|
|
const feedbacksAfter = await prisma.aiFeedback.findMany({
|
|
where: { noteId: note.id }
|
|
})
|
|
expect(feedbacksAfter.length).toBe(0)
|
|
})
|
|
|
|
test('should cascade delete feedback when user is deleted', async () => {
|
|
// This test would require a User model with proper setup
|
|
// For now, we'll skip as user deletion is a more complex operation
|
|
// that may involve authentication and authorization
|
|
})
|
|
})
|
|
|
|
describe('Index Performance', () => {
|
|
test('should have indexes on critical fields', async () => {
|
|
// Verify indexes exist by checking query plan or performance
|
|
// For SQLite, indexes are created in the migration
|
|
// This is more of a documentation test than a runtime test
|
|
|
|
const note = await prisma.note.create({
|
|
data: {
|
|
title: 'TEST_AI: Note for index test',
|
|
content: 'Test note',
|
|
userId: 'test-user-id'
|
|
}
|
|
})
|
|
|
|
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 noteId (should use index)
|
|
const byNoteId = await prisma.aiFeedback.findMany({
|
|
where: { noteId: note.id }
|
|
})
|
|
expect(byNoteId.length).toBe(2)
|
|
|
|
// Query by userId (should use index)
|
|
const byUserId = await prisma.aiFeedback.findMany({
|
|
where: { userId: 'test-user-id' }
|
|
})
|
|
expect(byUserId.length).toBeGreaterThanOrEqual(2)
|
|
|
|
// Query by feature (should use index)
|
|
const byFeature = await prisma.aiFeedback.findMany({
|
|
where: { feature: 'title_suggestion' }
|
|
})
|
|
expect(byFeature.length).toBeGreaterThanOrEqual(1)
|
|
})
|
|
})
|
|
|
|
describe('Data Types Validation', () => {
|
|
test('should accept valid aiProvider values', async () => {
|
|
const providers = ['openai', 'ollama', null]
|
|
|
|
for (const provider of providers) {
|
|
const note = await prisma.note.create({
|
|
data: {
|
|
title: `TEST_AI: Note with provider ${provider}`,
|
|
content: 'Test note',
|
|
userId: 'test-user-id',
|
|
aiProvider: provider
|
|
}
|
|
})
|
|
expect(note.aiProvider).toBe(provider)
|
|
}
|
|
})
|
|
|
|
test('should accept valid aiConfidence range (0-100)', async () => {
|
|
const confidences = [0, 50, 100]
|
|
|
|
for (const conf of confidences) {
|
|
const note = await prisma.note.create({
|
|
data: {
|
|
title: `TEST_AI: Note with confidence ${conf}`,
|
|
content: 'Test note',
|
|
userId: 'test-user-id',
|
|
aiConfidence: conf
|
|
}
|
|
})
|
|
expect(note.aiConfidence).toBe(conf)
|
|
}
|
|
})
|
|
|
|
test('should accept valid languageConfidence range (0.0-1.0)', async () => {
|
|
const confidences = [0.0, 0.5, 0.99, 1.0]
|
|
|
|
for (const conf of confidences) {
|
|
const note = await prisma.note.create({
|
|
data: {
|
|
title: `TEST_AI: Note with lang confidence ${conf}`,
|
|
content: 'Test note',
|
|
userId: 'test-user-id',
|
|
languageConfidence: conf
|
|
}
|
|
})
|
|
expect(note.languageConfidence).toBe(conf)
|
|
}
|
|
})
|
|
|
|
test('should accept valid ISO 639-1 language codes', async () => {
|
|
const languages = ['en', 'fr', 'es', 'de', 'fa', null]
|
|
|
|
for (const lang of languages) {
|
|
const note = await prisma.note.create({
|
|
data: {
|
|
title: `TEST_AI: Note in language ${lang}`,
|
|
content: 'Test note',
|
|
userId: 'test-user-id',
|
|
language: lang
|
|
}
|
|
})
|
|
expect(note.language).toBe(lang)
|
|
}
|
|
})
|
|
})
|
|
})
|