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' })), })); vi.mock('../../lib/auth.js', () => ({ extractAuth: extractAuthMock })); vi.mock('../../lib/product-config.js', () => ({ PRODUCT_ID: 'notelett' })); import { buildTestApp, resetMemoryDatastore } from '../../test-helpers.js'; import { noteArtifactRoutes } from './routes.js'; let app: FastifyInstance; beforeAll(async () => { app = await buildTestApp(noteArtifactRoutes); }); beforeEach(() => { resetMemoryDatastore(); }); afterAll(async () => { await app.close(); }); const validArtifact = { id: 'artifact-1', workspaceId: 'ws-1', noteId: 'note-1', artifactType: 'file', title: 'Launch brief.pdf', description: 'Ready for review', blobPath: 'notelett/user-1/launch-brief.pdf', contentType: 'application/pdf', sizeBytes: 2048, }; describe('note-artifacts routes — integration', () => { it('POST /note-artifacts creates an artifact and returns 201', async () => { const res = await app.inject({ method: 'POST', url: '/api/note-artifacts', payload: validArtifact }); expect(res.statusCode).toBe(201); const body = res.json(); expect(body.id).toBe('artifact-1'); expect(body.title).toBe('Launch brief.pdf'); expect(body.blobPath).toBe('notelett/user-1/launch-brief.pdf'); }); it('GET /note-artifacts lists artifacts by workspaceId', async () => { await app.inject({ method: 'POST', url: '/api/note-artifacts', payload: validArtifact }); const res = await app.inject({ method: 'GET', url: '/api/note-artifacts?workspaceId=ws-1' }); expect(res.statusCode).toBe(200); expect(res.json().items).toHaveLength(1); }); it('GET /note-artifacts requires workspaceId', async () => { const res = await app.inject({ method: 'GET', url: '/api/note-artifacts' }); expect(res.statusCode).toBe(400); }); it('GET /note-artifacts filters by noteId', async () => { await app.inject({ method: 'POST', url: '/api/note-artifacts', payload: validArtifact }); await app.inject({ method: 'POST', url: '/api/note-artifacts', payload: { ...validArtifact, id: 'artifact-2', noteId: 'note-2' }, }); const res = await app.inject({ method: 'GET', url: '/api/note-artifacts?workspaceId=ws-1¬eId=note-1' }); expect(res.json().items).toHaveLength(1); }); it('PATCH /note-artifacts/:id updates an artifact', async () => { await app.inject({ method: 'POST', url: '/api/note-artifacts', payload: validArtifact }); const res = await app.inject({ method: 'PATCH', url: '/api/note-artifacts/artifact-1?workspaceId=ws-1', payload: { title: 'Updated title' }, }); expect(res.statusCode).toBe(200); expect(res.json().title).toBe('Updated title'); }); it('PATCH /note-artifacts/:id returns 404 for missing artifact', async () => { const res = await app.inject({ method: 'PATCH', url: '/api/note-artifacts/missing?workspaceId=ws-1', payload: { title: 'X' }, }); expect(res.statusCode).toBe(404); }); it('POST /note-artifacts rejects invalid body', async () => { const res = await app.inject({ method: 'POST', url: '/api/note-artifacts', payload: { id: 'x' } }); expect(res.statusCode).toBe(400); }); });