Move src/ → apps/server/ and packages/web/ → apps/web/ to adopt standard monorepo conventions (apps/ for runnable apps, packages/ for reusable libraries). Update all config files, shared package imports, test fixtures, and documentation to reflect new paths. Key fixes: - Update workspace config to ["apps/*", "packages/*"] - Update tsconfig.json rootDir/include for apps/server/ - Add apps/web/** to vitest exclude list - Update drizzle.config.ts schema path - Fix ensure-schema.ts migration path detection (3 levels up in dev, 2 levels up in dist) - Fix tests/integration/cli-server.test.ts import paths - Update packages/shared imports to apps/server/ paths - Update all docs/ files with new paths
151 lines
4.6 KiB
TypeScript
151 lines
4.6 KiB
TypeScript
/**
|
|
* DrizzleInitiativeRepository Tests
|
|
*
|
|
* Tests for the Initiative repository adapter.
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import { DrizzleInitiativeRepository } from './initiative.js';
|
|
import { createTestDatabase } from './test-helpers.js';
|
|
import type { DrizzleDatabase } from '../../index.js';
|
|
|
|
describe('DrizzleInitiativeRepository', () => {
|
|
let db: DrizzleDatabase;
|
|
let repo: DrizzleInitiativeRepository;
|
|
|
|
beforeEach(() => {
|
|
db = createTestDatabase();
|
|
repo = new DrizzleInitiativeRepository(db);
|
|
});
|
|
|
|
describe('create', () => {
|
|
it('should create an initiative with generated id and timestamps', async () => {
|
|
const initiative = await repo.create({
|
|
name: 'Test Initiative',
|
|
});
|
|
|
|
expect(initiative.id).toBeDefined();
|
|
expect(initiative.id.length).toBeGreaterThan(0);
|
|
expect(initiative.name).toBe('Test Initiative');
|
|
expect(initiative.status).toBe('active');
|
|
expect(initiative.createdAt).toBeInstanceOf(Date);
|
|
expect(initiative.updatedAt).toBeInstanceOf(Date);
|
|
});
|
|
|
|
it('should use provided status', async () => {
|
|
const initiative = await repo.create({
|
|
name: 'Completed Initiative',
|
|
status: 'completed',
|
|
});
|
|
|
|
expect(initiative.status).toBe('completed');
|
|
});
|
|
});
|
|
|
|
describe('findById', () => {
|
|
it('should return null for non-existent initiative', async () => {
|
|
const result = await repo.findById('non-existent-id');
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should find an existing initiative', async () => {
|
|
const created = await repo.create({
|
|
name: 'Find Me',
|
|
});
|
|
|
|
const found = await repo.findById(created.id);
|
|
expect(found).not.toBeNull();
|
|
expect(found!.id).toBe(created.id);
|
|
expect(found!.name).toBe('Find Me');
|
|
});
|
|
});
|
|
|
|
describe('findAll', () => {
|
|
it('should return empty array initially', async () => {
|
|
const all = await repo.findAll();
|
|
expect(all).toEqual([]);
|
|
});
|
|
|
|
it('should return all initiatives', async () => {
|
|
await repo.create({ name: 'Initiative 1' });
|
|
await repo.create({ name: 'Initiative 2' });
|
|
await repo.create({ name: 'Initiative 3' });
|
|
|
|
const all = await repo.findAll();
|
|
expect(all.length).toBe(3);
|
|
});
|
|
});
|
|
|
|
describe('update', () => {
|
|
it('should update fields and updatedAt', async () => {
|
|
const created = await repo.create({
|
|
name: 'Original Name',
|
|
status: 'active',
|
|
});
|
|
|
|
// Small delay to ensure updatedAt differs
|
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
|
|
const updated = await repo.update(created.id, {
|
|
name: 'Updated Name',
|
|
status: 'completed',
|
|
});
|
|
|
|
expect(updated.name).toBe('Updated Name');
|
|
expect(updated.status).toBe('completed');
|
|
expect(updated.updatedAt.getTime()).toBeGreaterThanOrEqual(created.updatedAt.getTime());
|
|
});
|
|
|
|
it('should throw for non-existent initiative', async () => {
|
|
await expect(
|
|
repo.update('non-existent-id', { name: 'New Name' })
|
|
).rejects.toThrow('Initiative not found');
|
|
});
|
|
});
|
|
|
|
describe('delete', () => {
|
|
it('should delete an existing initiative', async () => {
|
|
const created = await repo.create({ name: 'To Delete' });
|
|
|
|
await repo.delete(created.id);
|
|
|
|
const found = await repo.findById(created.id);
|
|
expect(found).toBeNull();
|
|
});
|
|
|
|
it('should throw for non-existent initiative', async () => {
|
|
await expect(repo.delete('non-existent-id')).rejects.toThrow(
|
|
'Initiative not found'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('findByStatus', () => {
|
|
it('should return empty array for no matches', async () => {
|
|
await repo.create({ name: 'Active 1', status: 'active' });
|
|
|
|
const completed = await repo.findByStatus('completed');
|
|
expect(completed).toEqual([]);
|
|
});
|
|
|
|
it('should filter by status', async () => {
|
|
await repo.create({ name: 'Active 1', status: 'active' });
|
|
await repo.create({ name: 'Active 2', status: 'active' });
|
|
await repo.create({ name: 'Completed', status: 'completed' });
|
|
await repo.create({ name: 'Archived', status: 'archived' });
|
|
|
|
const active = await repo.findByStatus('active');
|
|
expect(active).toHaveLength(2);
|
|
expect(active.every((i) => i.status === 'active')).toBe(true);
|
|
|
|
const completed = await repo.findByStatus('completed');
|
|
expect(completed).toHaveLength(1);
|
|
expect(completed[0].name).toBe('Completed');
|
|
|
|
const archived = await repo.findByStatus('archived');
|
|
expect(archived).toHaveLength(1);
|
|
expect(archived[0].name).toBe('Archived');
|
|
});
|
|
});
|
|
});
|