Phase 2 of the execution roadmap:
- Add DELETE endpoints for notes (soft-delete), workspaces, tasks, artifacts, relationships
- Add requireWriter() role enforcement on all write routes (POST/PATCH/DELETE)
- Activate trackEvent() telemetry on note.created, note.updated, note.archived, workspace.created
- Gate notes search behind isFeatureEnabled('notes.enabled')
- Update all test mocks to include role and new auth exports
Made-with: Cursor
91 lines
3.2 KiB
TypeScript
91 lines
3.2 KiB
TypeScript
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);
|
|
});
|
|
});
|