feat(09-03): create database seeding fixture for E2E tests

- Add test fixture with seededDb for predictable test data
- Include 5 entries: tasks and thoughts with various states
- Include 3 tags with entry-tag relationships
- Export extended test with fixtures from tests/e2e/index.ts
- Install drizzle-seed dependency
This commit is contained in:
Thomas Richter
2026-02-03 23:35:23 +01:00
parent 20d9ebf2ff
commit 283a9214ad
4 changed files with 218 additions and 0 deletions

174
tests/e2e/fixtures/db.ts Normal file
View File

@@ -0,0 +1,174 @@
/**
* Database seeding fixture for E2E tests
*
* Uses direct SQL for cleanup and drizzle for typed inserts.
* Each test gets a known starting state that can be asserted against.
*
* Note: drizzle-seed is installed but we use manual cleanup for better control
* and to avoid type compatibility issues with reset().
*/
import { test as base } from '@playwright/test';
import Database from 'better-sqlite3';
import { drizzle } from 'drizzle-orm/better-sqlite3';
import * as schema from '../../../src/lib/server/db/schema';
// Test database path - same as application for E2E tests
const DATA_DIR = process.env.DATA_DIR || './data';
const DB_PATH = `${DATA_DIR}/taskplaner.db`;
// Known test data with predictable IDs for assertions
export const testData = {
entries: [
{
id: 'test-entry-001',
title: null,
content: 'Buy groceries for the week',
type: 'task' as const,
status: 'open' as const,
pinned: false,
dueDate: '2026-02-10',
createdAt: '2026-02-01T10:00:00.000Z',
updatedAt: '2026-02-01T10:00:00.000Z'
},
{
id: 'test-entry-002',
title: null,
content: 'Completed task from yesterday',
type: 'task' as const,
status: 'done' as const,
pinned: false,
dueDate: null,
createdAt: '2026-02-02T09:00:00.000Z',
updatedAt: '2026-02-02T15:00:00.000Z'
},
{
id: 'test-entry-003',
title: null,
content: 'Important pinned thought about project architecture',
type: 'thought' as const,
status: null,
pinned: true,
dueDate: null,
createdAt: '2026-02-01T08:00:00.000Z',
updatedAt: '2026-02-01T08:00:00.000Z'
},
{
id: 'test-entry-004',
title: null,
content: 'Meeting notes with stakeholders',
type: 'thought' as const,
status: null,
pinned: false,
dueDate: null,
createdAt: '2026-02-03T14:00:00.000Z',
updatedAt: '2026-02-03T14:00:00.000Z'
},
{
id: 'test-entry-005',
title: null,
content: 'Review pull request for feature branch',
type: 'task' as const,
status: 'open' as const,
pinned: false,
dueDate: '2026-02-05',
createdAt: '2026-02-03T11:00:00.000Z',
updatedAt: '2026-02-03T11:00:00.000Z'
}
],
tags: [
{
id: 'test-tag-001',
name: 'work',
createdAt: '2026-02-01T00:00:00.000Z'
},
{
id: 'test-tag-002',
name: 'personal',
createdAt: '2026-02-01T00:00:00.000Z'
},
{
id: 'test-tag-003',
name: 'urgent',
createdAt: '2026-02-01T00:00:00.000Z'
}
],
entryTags: [
{ entryId: 'test-entry-001', tagId: 'test-tag-002' }, // groceries -> personal
{ entryId: 'test-entry-003', tagId: 'test-tag-001' }, // architecture -> work
{ entryId: 'test-entry-004', tagId: 'test-tag-001' }, // meeting notes -> work
{ entryId: 'test-entry-005', tagId: 'test-tag-001' }, // PR review -> work
{ entryId: 'test-entry-005', tagId: 'test-tag-003' } // PR review -> urgent
]
};
/**
* Clear all data from the database (respecting foreign key order)
*/
function clearDatabase(sqlite: Database.Database) {
// Delete in order that respects foreign key constraints
sqlite.exec('DELETE FROM entry_tags');
sqlite.exec('DELETE FROM images');
sqlite.exec('DELETE FROM tags');
sqlite.exec('DELETE FROM entries');
}
/**
* Seed the database with known test data
*/
async function seedDatabase() {
const sqlite = new Database(DB_PATH);
sqlite.pragma('journal_mode = WAL');
const db = drizzle(sqlite, { schema });
// Clear existing data
clearDatabase(sqlite);
// Insert test entries
for (const entry of testData.entries) {
db.insert(schema.entries).values(entry).run();
}
// Insert test tags
for (const tag of testData.tags) {
db.insert(schema.tags).values(tag).run();
}
// Insert entry-tag relationships
for (const entryTag of testData.entryTags) {
db.insert(schema.entryTags).values(entryTag).run();
}
sqlite.close();
}
/**
* Clean up test data after tests
*/
async function cleanupDatabase() {
const sqlite = new Database(DB_PATH);
sqlite.pragma('journal_mode = WAL');
// Clear all test data
clearDatabase(sqlite);
sqlite.close();
}
// Export fixture type for TypeScript
export type SeededDbFixture = {
testData: typeof testData;
};
// Extend Playwright test with seeded database fixture
export const test = base.extend<{ seededDb: SeededDbFixture }>({
seededDb: async ({}, use) => {
// Setup: seed database before test
await seedDatabase();
// Provide test data for assertions
await use({ testData });
// Teardown: clean up after test
await cleanupDatabase();
}
});

7
tests/e2e/index.ts Normal file
View File

@@ -0,0 +1,7 @@
/**
* E2E test exports with database fixtures
*
* Import { test, expect } from this file to get tests with seeded database.
*/
export { test, testData } from './fixtures/db';
export { expect } from '@playwright/test';