- 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
574 lines
17 KiB
TypeScript
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)
|
|
})
|
|
})
|
|
})
|