sh-mcp/packages/tasks/test/dev-client.test.ts

120 lines
4.8 KiB
TypeScript
Raw Normal View History

import { describe, it, expect } from 'vitest';
import { InMemoryTasksClient } from '../src/dev-client.js';
const LAUREN = 'lauren@seahavenind.com';
const ADAM = 'adam@seahavenind.com';
describe('InMemoryTasksClient', () => {
describe('createTask', () => {
it('returns a task with a generated id and completed:false', async () => {
const client = new InMemoryTasksClient();
const task = await client.createTask({ sub: LAUREN, title: 'Buy gloves' });
expect(task.taskId).toMatch(/^task-\d{4}$/);
expect(task.sub).toBe(LAUREN);
expect(task.title).toBe('Buy gloves');
expect(task.completed).toBe(false);
expect(task.createdAt).toBeDefined();
expect(task.description).toBeUndefined();
});
it('carries an optional description through', async () => {
const client = new InMemoryTasksClient();
const task = await client.createTask({ sub: LAUREN, title: 'T', description: 'detail' });
expect(task.description).toBe('detail');
});
it('a task created for sub A is not visible to sub B (partitioned)', async () => {
const client = new InMemoryTasksClient();
const task = await client.createTask({ sub: LAUREN, title: 'Lauren only' });
const adamTasks = await client.listTasks({ sub: ADAM, includeCompleted: true });
expect(adamTasks.map((t) => t.taskId)).not.toContain(task.taskId);
const laurenTasks = await client.listTasks({ sub: LAUREN, includeCompleted: true });
expect(laurenTasks.map((t) => t.taskId)).toContain(task.taskId);
});
it('creates a task for a previously-unknown sub', async () => {
const client = new InMemoryTasksClient();
const task = await client.createTask({ sub: 'fresh@seahavenind.com', title: 'First' });
const tasks = await client.listTasks({
sub: 'fresh@seahavenind.com',
includeCompleted: true,
});
expect(tasks.map((t) => t.taskId)).toEqual([task.taskId]);
});
});
describe('listTasks', () => {
it('omits completed tasks by default', async () => {
const client = new InMemoryTasksClient();
const tasks = await client.listTasks({ sub: LAUREN });
const ids = tasks.map((t) => t.taskId);
expect(ids).toContain('task-0001'); // open
expect(ids).not.toContain('task-0002'); // completed
});
it('includes completed tasks when includeCompleted is true', async () => {
const client = new InMemoryTasksClient();
const tasks = await client.listTasks({ sub: LAUREN, includeCompleted: true });
const ids = tasks.map((t) => t.taskId);
expect(ids).toContain('task-0001');
expect(ids).toContain('task-0002');
});
it('returns an empty list for an unknown sub', async () => {
const client = new InMemoryTasksClient();
expect(await client.listTasks({ sub: 'nobody@example.com' })).toEqual([]);
});
});
describe('completeTask', () => {
it('sets completed and completedAt', async () => {
const client = new InMemoryTasksClient();
const completed = await client.completeTask({ sub: LAUREN, taskId: 'task-0001' });
expect(completed.completed).toBe(true);
expect(completed.completedAt).toBeDefined();
// now omitted from the default (open-only) listing
const open = await client.listTasks({ sub: LAUREN });
expect(open.map((t) => t.taskId)).not.toContain('task-0001');
});
it('throws for an unknown taskId', async () => {
const client = new InMemoryTasksClient();
await expect(client.completeTask({ sub: LAUREN, taskId: 'task-missing' })).rejects.toThrow(
/not found/,
);
});
it('throws when the task belongs to another sub', async () => {
const client = new InMemoryTasksClient();
await expect(client.completeTask({ sub: ADAM, taskId: 'task-0001' })).rejects.toThrow(
/not found/,
);
});
});
describe('deleteTask', () => {
it('removes the task so subsequent listings omit it', async () => {
const client = new InMemoryTasksClient();
await client.deleteTask({ sub: LAUREN, taskId: 'task-0001' });
const tasks = await client.listTasks({ sub: LAUREN, includeCompleted: true });
expect(tasks.map((t) => t.taskId)).not.toContain('task-0001');
});
it('is a no-op for an unknown taskId', async () => {
const client = new InMemoryTasksClient();
await expect(
client.deleteTask({ sub: LAUREN, taskId: 'task-missing' }),
).resolves.toBeUndefined();
const tasks = await client.listTasks({ sub: LAUREN, includeCompleted: true });
expect(tasks).toHaveLength(2);
});
it('is a no-op for an unknown sub', async () => {
const client = new InMemoryTasksClient();
await expect(
client.deleteTask({ sub: 'nobody@example.com', taskId: 'task-0001' }),
).resolves.toBeUndefined();
});
});
});