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

574 lines
17 KiB
TypeScript

/**
* Performance Tests
* Validates that migrations complete within acceptable time limits
* Tests scalability with various data sizes
*/
import { PrismaClient } from '@prisma/client'
import {
setupTestEnvironment,
createTestPrismaClient,
initializeTestDatabase,
cleanupTestDatabase,
createSampleNotes,
measureExecutionTime,
getDatabaseSize
} from './setup'
describe('Performance Tests', () => {
let prisma: PrismaClient
beforeAll(async () => {
await setupTestEnvironment()
prisma = createTestPrismaClient()
await initializeTestDatabase(prisma)
})
afterAll(async () => {
await cleanupTestDatabase(prisma)
})
describe('Empty Migration Performance', () => {
test('should complete empty database migration quickly', async () => {
// Clean up any existing data
await prisma.note.deleteMany({})
// Measure time to "migrate" empty database
const { result, duration } = await measureExecutionTime(async () => {
const noteCount = await prisma.note.count()
return { count: noteCount }
})
// Empty migration should complete instantly (< 1 second)
expect(duration).toBeLessThan(1000)
expect(result.count).toBe(0)
})
})
describe('Small Dataset Performance (10 notes)', () => {
beforeEach(async () => {
await prisma.note.deleteMany({})
})
test('should complete migration for 10 notes within 1 second', async () => {
// Create 10 notes
const { result: notes, duration: createDuration } = await measureExecutionTime(async () => {
return await createSampleNotes(prisma, 10)
})
expect(notes.length).toBe(10)
expect(createDuration).toBeLessThan(1000)
// Measure query performance
const { result, duration: queryDuration } = await measureExecutionTime(async () => {
return await prisma.note.findMany()
})
expect(result.length).toBe(10)
expect(queryDuration).toBeLessThan(500)
})
test('should complete create operation for 10 notes within 1 second', async () => {
const { result, duration } = await measureExecutionTime(async () => {
const notes = []
for (let i = 0; i < 10; i++) {
const note = await prisma.note.create({
data: {
title: `Perf Test Note ${i}`,
content: `Test content ${i}`,
userId: 'test-user-id',
order: i
}
})
notes.push(note)
}
return notes
})
expect(result.length).toBe(10)
expect(duration).toBeLessThan(1000)
})
test('should complete update operation for 10 notes within 1 second', async () => {
// Create notes first
await createSampleNotes(prisma, 10)
// Measure update performance
const { result, duration } = await measureExecutionTime(async () => {
return await prisma.note.updateMany({
data: { isPinned: true },
where: { title: { contains: 'Test Note' } }
})
})
expect(duration).toBeLessThan(1000)
})
})
describe('Medium Dataset Performance (100 notes)', () => {
beforeEach(async () => {
await prisma.note.deleteMany({})
})
test('should complete migration for 100 notes within 5 seconds', async () => {
// Create 100 notes
const { result: notes, duration: createDuration } = await measureExecutionTime(async () => {
return await createSampleNotes(prisma, 100)
})
expect(notes.length).toBe(100)
expect(createDuration).toBeLessThan(5000)
// Measure query performance
const { result, duration: queryDuration } = await measureExecutionTime(async () => {
return await prisma.note.findMany()
})
expect(result.length).toBe(100)
expect(queryDuration).toBeLessThan(1000)
})
test('should complete create operation for 100 notes within 5 seconds', async () => {
const { result, duration } = await measureExecutionTime(async () => {
const notes = []
for (let i = 0; i < 100; i++) {
const note = await prisma.note.create({
data: {
title: `Perf Test Note ${i}`,
content: `Test content ${i}`,
userId: 'test-user-id',
order: i
}
})
notes.push(note)
}
return notes
})
expect(result.length).toBe(100)
expect(duration).toBeLessThan(5000)
})
test('should complete batch insert for 100 notes within 2 seconds', async () => {
const notesData = Array.from({ length: 100 }, (_, i) => ({
title: `Batch Note ${i}`,
content: `Batch content ${i}`,
userId: 'test-user-id',
order: i
}))
const { result, duration } = await measureExecutionTime(async () => {
return await prisma.note.createMany({
data: notesData
})
})
expect(result.count).toBe(100)
expect(duration).toBeLessThan(2000)
})
test('should complete filtered query for 100 notes within 500ms', async () => {
await createSampleNotes(prisma, 100)
const { result, duration } = await measureExecutionTime(async () => {
return await prisma.note.findMany({
where: {
isPinned: true
}
})
})
expect(duration).toBeLessThan(500)
})
})
describe('Target Dataset Performance (1,000 notes)', () => {
beforeEach(async () => {
await prisma.note.deleteMany({})
})
test('should complete migration for 1,000 notes within 30 seconds', async () => {
// Create 1,000 notes in batches for better performance
const { result: notes, duration: createDuration } = await measureExecutionTime(async () => {
const allNotes = []
const batchSize = 100
const totalNotes = 1000
for (let batch = 0; batch < totalNotes / batchSize; batch++) {
const batchData = Array.from({ length: batchSize }, (_, i) => ({
title: `Perf Note ${batch * batchSize + i}`,
content: `Test content ${batch * batchSize + i}`,
userId: 'test-user-id',
order: batch * batchSize + i
}))
await prisma.note.createMany({ data: batchData })
}
return await prisma.note.findMany()
})
expect(notes.length).toBe(1000)
expect(createDuration).toBeLessThan(30000)
})
test('should complete batch insert for 1,000 notes within 10 seconds', async () => {
const notesData = Array.from({ length: 1000 }, (_, i) => ({
title: `Batch Note ${i}`,
content: `Batch content ${i}`,
userId: 'test-user-id',
order: i
}))
const { result, duration } = await measureExecutionTime(async () => {
return await prisma.note.createMany({
data: notesData
})
})
expect(result.count).toBe(1000)
expect(duration).toBeLessThan(10000)
})
test('should complete query for 1,000 notes within 1 second', async () => {
const notesData = Array.from({ length: 1000 }, (_, i) => ({
title: `Query Test Note ${i}`,
content: `Query test content ${i}`,
userId: 'test-user-id',
order: i
}))
await prisma.note.createMany({ data: notesData })
const { result, duration } = await measureExecutionTime(async () => {
return await prisma.note.findMany()
})
expect(result.length).toBe(1000)
expect(duration).toBeLessThan(1000)
})
test('should complete filtered query for 1,000 notes within 1 second', async () => {
// Create notes with various pinned states
const notesData = Array.from({ length: 1000 }, (_, i) => ({
title: `Filter Test Note ${i}`,
content: `Filter test content ${i}`,
userId: 'test-user-id',
order: i,
isPinned: i % 3 === 0 // Every 3rd note is pinned
}))
await prisma.note.createMany({ data: notesData })
const { result, duration } = await measureExecutionTime(async () => {
return await prisma.note.findMany({
where: {
isPinned: true
}
})
})
expect(result.length).toBeGreaterThan(0)
expect(duration).toBeLessThan(1000)
})
test('should complete indexed query for 1,000 notes within 500ms', async () => {
// Create notes
const notesData = Array.from({ length: 1000 }, (_, i) => ({
title: `Index Test Note ${i}`,
content: `Index test content ${i}`,
userId: 'test-user-id',
order: i,
isPinned: i % 2 === 0
}))
await prisma.note.createMany({ data: notesData })
// Query using indexed field (isPinned)
const { result, duration } = await measureExecutionTime(async () => {
return await prisma.note.findMany({
where: {
isPinned: true
},
orderBy: {
order: 'asc'
}
})
})
expect(result.length).toBeGreaterThan(0)
expect(duration).toBeLessThan(500)
})
})
describe('Stress Test Performance (10,000 notes)', () => {
beforeEach(async () => {
await prisma.note.deleteMany({})
})
test('should complete batch insert for 10,000 notes within 30 seconds', async () => {
const notesData = Array.from({ length: 10000 }, (_, i) => ({
title: `Stress Note ${i}`,
content: `Stress test content ${i}`,
userId: 'test-user-id',
order: i
}))
const { result, duration } = await measureExecutionTime(async () => {
return await prisma.note.createMany({
data: notesData
})
})
expect(result.count).toBe(10000)
expect(duration).toBeLessThan(30000)
})
test('should complete query for 10,000 notes within 2 seconds', async () => {
const notesData = Array.from({ length: 10000 }, (_, i) => ({
title: `Stress Query Note ${i}`,
content: `Stress query content ${i}`,
userId: 'test-user-id',
order: i
}))
await prisma.note.createMany({ data: notesData })
const { result, duration } = await measureExecutionTime(async () => {
return await prisma.note.findMany({
take: 100 // Limit to 100 for performance
})
})
expect(result.length).toBe(100)
expect(duration).toBeLessThan(2000)
})
test('should handle pagination for 10,000 notes efficiently', async () => {
const notesData = Array.from({ length: 10000 }, (_, i) => ({
title: `Pagination Note ${i}`,
content: `Pagination content ${i}`,
userId: 'test-user-id',
order: i
}))
await prisma.note.createMany({ data: notesData })
const { result, duration } = await measureExecutionTime(async () => {
return await prisma.note.findMany({
skip: 100,
take: 50
})
})
expect(result.length).toBe(50)
expect(duration).toBeLessThan(1000)
})
})
describe('AI Features Performance', () => {
beforeEach(async () => {
await prisma.note.deleteMany({})
await prisma.aiFeedback.deleteMany({})
})
test('should create AI-enabled notes efficiently (100 notes)', async () => {
const { result, duration } = await measureExecutionTime(async () => {
const notes = []
for (let i = 0; i < 100; i++) {
const note = await prisma.note.create({
data: {
title: `AI Note ${i}`,
content: `AI content ${i}`,
userId: 'test-user-id',
autoGenerated: i % 2 === 0,
aiProvider: i % 3 === 0 ? 'openai' : 'ollama',
aiConfidence: 70 + i,
language: i % 2 === 0 ? 'en' : 'fr',
languageConfidence: 0.85 + (i * 0.001),
lastAiAnalysis: new Date(),
order: i
}
})
notes.push(note)
}
return notes
})
expect(result.length).toBe(100)
expect(duration).toBeLessThan(5000)
})
test('should query by AI fields efficiently (100 notes)', async () => {
// Create AI notes
const notes = []
for (let i = 0; i < 100; i++) {
const note = await prisma.note.create({
data: {
title: `AI Query Note ${i}`,
content: `Content ${i}`,
userId: 'test-user-id',
autoGenerated: i % 2 === 0,
aiProvider: i % 3 === 0 ? 'openai' : 'ollama',
order: i
}
})
notes.push(note)
}
// Query by autoGenerated
const { result: autoGenerated, duration: duration1 } = await measureExecutionTime(async () => {
return await prisma.note.findMany({
where: { autoGenerated: true }
})
})
expect(autoGenerated.length).toBeGreaterThan(0)
expect(duration1).toBeLessThan(500)
// Query by aiProvider
const { result: openaiNotes, duration: duration2 } = await measureExecutionTime(async () => {
return await prisma.note.findMany({
where: { aiProvider: 'openai' }
})
})
expect(openaiNotes.length).toBeGreaterThan(0)
expect(duration2).toBeLessThan(500)
})
test('should create AI feedback efficiently (100 feedback entries)', async () => {
// Create a note
const note = await prisma.note.create({
data: {
title: 'Feedback Performance Note',
content: 'Test content',
userId: 'test-user-id'
}
})
const { result, duration } = await measureExecutionTime(async () => {
const feedbacks = []
for (let i = 0; i < 100; i++) {
const feedback = await prisma.aiFeedback.create({
data: {
noteId: note.id,
userId: 'test-user-id',
feedbackType: i % 3 === 0 ? 'thumbs_up' : 'thumbs_down',
feature: 'title_suggestion',
originalContent: `Feedback ${i}`,
metadata: JSON.stringify({
aiProvider: 'openai',
confidence: 70 + i,
timestamp: new Date().toISOString()
})
}
})
feedbacks.push(feedback)
}
return feedbacks
})
expect(result.length).toBe(100)
expect(duration).toBeLessThan(5000)
})
test('should query feedback by note efficiently (100 feedback entries)', async () => {
const note = await prisma.note.create({
data: {
title: 'Feedback Query Note',
content: 'Test content',
userId: 'test-user-id'
}
})
// Create feedback
for (let i = 0; i < 100; i++) {
await prisma.aiFeedback.create({
data: {
noteId: note.id,
userId: 'test-user-id',
feedbackType: 'thumbs_up',
feature: 'title_suggestion',
originalContent: `Feedback ${i}`
}
})
}
// Query by noteId (should use index)
const { result, duration } = await measureExecutionTime(async () => {
return await prisma.aiFeedback.findMany({
where: { noteId: note.id },
orderBy: { createdAt: 'asc' }
})
})
expect(result.length).toBe(100)
expect(duration).toBeLessThan(500)
})
})
describe('Database Size Performance', () => {
test('should track database size growth', async () => {
// Get initial size
const initialSize = await getDatabaseSize(prisma)
// Add 100 notes
const notesData = Array.from({ length: 100 }, (_, i) => ({
title: `Size Test Note ${i}`,
content: `Size test content ${i}`.repeat(10), // Larger content
userId: 'test-user-id',
order: i
}))
await prisma.note.createMany({ data: notesData })
// Get size after adding notes
const sizeAfter = await getDatabaseSize(prisma)
// Database should have grown
expect(sizeAfter).toBeGreaterThan(initialSize)
})
test('should handle large content efficiently', async () => {
const largeContent = 'A'.repeat(10000) // 10KB per note
const { result, duration } = await measureExecutionTime(async () => {
return await prisma.note.create({
data: {
title: 'Large Content Note',
content: largeContent,
userId: 'test-user-id'
}
})
})
expect(result.content).toHaveLength(10000)
expect(duration).toBeLessThan(1000)
})
})
describe('Concurrent Operations Performance', () => {
test('should handle multiple concurrent reads', async () => {
// Create test data
await createSampleNotes(prisma, 100)
// Measure concurrent read performance
const { duration } = await measureExecutionTime(async () => {
const promises = [
prisma.note.findMany({ take: 10 }),
prisma.note.findMany({ take: 10, skip: 10 }),
prisma.note.findMany({ take: 10, skip: 20 }),
prisma.note.findMany({ take: 10, skip: 30 }),
prisma.note.findMany({ take: 10, skip: 40 })
]
await Promise.all(promises)
})
// All concurrent reads should complete quickly
expect(duration).toBeLessThan(2000)
})
})
})