feat(mvp): phase 8 - integration, testing & deployment
Fix all build/type/lint errors (zod 3.25 compat wrapper, Svelte 5 fixes), write 115 unit tests across 10 test files, expand seed script with demo data, update Docker config with migration on startup.
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
vi.mock('../../prisma.js', () => ({
|
||||
prisma: {
|
||||
board: {
|
||||
findMany: vi.fn(),
|
||||
findUnique: vi.fn(),
|
||||
findFirst: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
updateMany: vi.fn(),
|
||||
delete: vi.fn()
|
||||
},
|
||||
section: {
|
||||
findUnique: vi.fn(),
|
||||
findFirst: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
delete: vi.fn()
|
||||
},
|
||||
widget: {
|
||||
findUnique: vi.fn(),
|
||||
findFirst: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
delete: vi.fn()
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
import { prisma } from '../../prisma.js';
|
||||
import * as boardService from '../boardService.js';
|
||||
|
||||
const mockBoard = prisma.board as unknown as Record<string, ReturnType<typeof vi.fn>>;
|
||||
const mockSection = prisma.section as unknown as Record<string, ReturnType<typeof vi.fn>>;
|
||||
const mockWidget = prisma.widget as unknown as Record<string, ReturnType<typeof vi.fn>>;
|
||||
|
||||
describe('boardService', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('findAllBoards', () => {
|
||||
it('returns all boards', async () => {
|
||||
const boards = [{ id: '1', name: 'Main', _count: { sections: 2 } }];
|
||||
mockBoard.findMany.mockResolvedValue(boards);
|
||||
|
||||
const result = await boardService.findAllBoards();
|
||||
expect(result).toEqual(boards);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findBoardById', () => {
|
||||
it('returns board with sections and widgets', async () => {
|
||||
const board = { id: '1', name: 'Main', sections: [] };
|
||||
mockBoard.findUnique.mockResolvedValue(board);
|
||||
|
||||
const result = await boardService.findBoardById('1');
|
||||
expect(result.name).toBe('Main');
|
||||
});
|
||||
|
||||
it('throws when not found', async () => {
|
||||
mockBoard.findUnique.mockResolvedValue(null);
|
||||
await expect(boardService.findBoardById('missing')).rejects.toThrow('Board not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createBoard', () => {
|
||||
it('creates a board', async () => {
|
||||
mockBoard.create.mockResolvedValue({ id: '1', name: 'New Board' });
|
||||
|
||||
const result = await boardService.createBoard({ name: 'New Board' });
|
||||
expect(result.name).toBe('New Board');
|
||||
});
|
||||
|
||||
it('unsets other defaults when creating a default board', async () => {
|
||||
mockBoard.updateMany.mockResolvedValue({ count: 1 });
|
||||
mockBoard.create.mockResolvedValue({ id: '1', name: 'Default', isDefault: true });
|
||||
|
||||
await boardService.createBoard({ name: 'Default', isDefault: true });
|
||||
|
||||
expect(mockBoard.updateMany).toHaveBeenCalledWith({
|
||||
where: { isDefault: true },
|
||||
data: { isDefault: false }
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateBoard', () => {
|
||||
it('updates board fields', async () => {
|
||||
mockBoard.findUnique.mockResolvedValue({ id: '1' });
|
||||
mockBoard.update.mockResolvedValue({ id: '1', name: 'Updated' });
|
||||
|
||||
const result = await boardService.updateBoard('1', { name: 'Updated' });
|
||||
expect(result.name).toBe('Updated');
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeBoard', () => {
|
||||
it('deletes a board', async () => {
|
||||
mockBoard.findUnique.mockResolvedValue({ id: '1' });
|
||||
mockBoard.delete.mockResolvedValue({});
|
||||
|
||||
await boardService.removeBoard('1');
|
||||
expect(mockBoard.delete).toHaveBeenCalledWith({ where: { id: '1' } });
|
||||
});
|
||||
});
|
||||
|
||||
describe('createSection', () => {
|
||||
it('creates a section with auto-calculated order', async () => {
|
||||
mockSection.findFirst.mockResolvedValue({ order: 2 });
|
||||
mockSection.create.mockResolvedValue({
|
||||
id: 's1',
|
||||
title: 'Media',
|
||||
order: 3
|
||||
});
|
||||
|
||||
const result = await boardService.createSection({
|
||||
boardId: 'b1',
|
||||
title: 'Media'
|
||||
});
|
||||
|
||||
expect(result.order).toBe(3);
|
||||
});
|
||||
|
||||
it('starts order at 0 for first section', async () => {
|
||||
mockSection.findFirst.mockResolvedValue(null);
|
||||
mockSection.create.mockResolvedValue({
|
||||
id: 's1',
|
||||
title: 'First',
|
||||
order: 0
|
||||
});
|
||||
|
||||
await boardService.createSection({ boardId: 'b1', title: 'First' });
|
||||
|
||||
expect(mockSection.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: expect.objectContaining({ order: 0 })
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createWidget', () => {
|
||||
it('creates a widget', async () => {
|
||||
mockWidget.findFirst.mockResolvedValue(null);
|
||||
mockWidget.create.mockResolvedValue({
|
||||
id: 'w1',
|
||||
type: 'app',
|
||||
order: 0
|
||||
});
|
||||
|
||||
const result = await boardService.createWidget({
|
||||
sectionId: 's1',
|
||||
type: 'app'
|
||||
});
|
||||
|
||||
expect(result.type).toBe('app');
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeWidget', () => {
|
||||
it('deletes a widget', async () => {
|
||||
mockWidget.findUnique.mockResolvedValue({ id: 'w1' });
|
||||
mockWidget.delete.mockResolvedValue({});
|
||||
|
||||
await boardService.removeWidget('w1');
|
||||
expect(mockWidget.delete).toHaveBeenCalledWith({ where: { id: 'w1' } });
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user