import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import type { FastifyInstance } from 'fastify'; const { extractAuthMock } = vi.hoisted(() => ({ extractAuthMock: vi.fn(async () => ({ sub: 'user_1', type: 'access', role: 'editor' })), })); vi.mock('../../lib/auth.js', () => ({ extractAuth: extractAuthMock, requireWriter: extractAuthMock })); vi.mock('../../lib/product-config.js', () => ({ PRODUCT_ID: 'notelett' })); vi.mock('../../lib/telemetry.js', () => ({ trackEvent: vi.fn() })); import { buildTestApp, resetMemoryDatastore } from '../../test-helpers.js'; import { workspaceRoutes } from './routes.js'; let app: FastifyInstance; beforeAll(async () => { app = await buildTestApp(workspaceRoutes); }); beforeEach(() => { resetMemoryDatastore(); }); afterAll(async () => { await app.close(); }); const validWorkspace = { id: 'ws-1', name: 'Test Workspace', description: 'A workspace for testing', members: [], }; describe('workspace routes — integration', () => { it('POST /workspaces creates a workspace and returns 201', async () => { const res = await app.inject({ method: 'POST', url: '/api/workspaces', payload: validWorkspace }); expect(res.statusCode).toBe(201); const body = res.json(); expect(body.id).toBe('ws-1'); expect(body.name).toBe('Test Workspace'); expect(body.members[0].userId).toBe('user_1'); expect(body.members[0].role).toBe('owner'); }); it('GET /workspaces lists workspaces', async () => { await app.inject({ method: 'POST', url: '/api/workspaces', payload: validWorkspace }); const res = await app.inject({ method: 'GET', url: '/api/workspaces' }); expect(res.statusCode).toBe(200); expect(res.json().items).toHaveLength(1); }); it('GET /workspaces/:id returns a workspace', async () => { await app.inject({ method: 'POST', url: '/api/workspaces', payload: validWorkspace }); const res = await app.inject({ method: 'GET', url: '/api/workspaces/ws-1' }); expect(res.statusCode).toBe(200); expect(res.json().name).toBe('Test Workspace'); }); it('GET /workspaces/:id returns 404 for missing workspace', async () => { const res = await app.inject({ method: 'GET', url: '/api/workspaces/missing' }); expect(res.statusCode).toBe(404); }); it('PATCH /workspaces/:id updates the workspace', async () => { await app.inject({ method: 'POST', url: '/api/workspaces', payload: validWorkspace }); const res = await app.inject({ method: 'PATCH', url: '/api/workspaces/ws-1', payload: { name: 'Updated' }, }); expect(res.statusCode).toBe(200); expect(res.json().name).toBe('Updated'); }); it('GET /workspaces/summaries returns workspaces with noteCount', async () => { await app.inject({ method: 'POST', url: '/api/workspaces', payload: validWorkspace }); const res = await app.inject({ method: 'GET', url: '/api/workspaces/summaries' }); expect(res.statusCode).toBe(200); const body = res.json(); expect(body.items).toHaveLength(1); expect(body.items[0].noteCount).toBe(0); }); it('POST /workspaces rejects invalid body', async () => { const res = await app.inject({ method: 'POST', url: '/api/workspaces', payload: { id: 'x' } }); expect(res.statusCode).toBe(400); }); });