agent-ecosystem/test/main/services/team/TeamInboxWriter.test.ts
iliya 3c1ef54ce2 feat: enhance team member management with color coding and improved prompts
- Added member color assignment using a new utility function for better visual representation in the UI.
- Updated team member prompts to encourage brief introductions, aligning with project guidelines.
- Introduced a draft persistence mechanism for message and task creation dialogs to enhance user experience.
- Refactored team configuration handling to support new member structures and improve data integrity.

This update aims to streamline team interactions and improve the overall user experience in team management features.
2026-02-22 23:36:11 +02:00

133 lines
3.7 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
const hoisted = vi.hoisted(() => {
const files = new Map<string, string>();
let idCounter = 0;
let dropWrites = 0;
// Normalize path separators so tests pass on Windows (backslash → forward slash)
const norm = (p: string): string => p.replace(/\\/g, '/');
const readFile = vi.fn(async (filePath: string) => {
const data = files.get(norm(filePath));
if (data === undefined) {
const error = new Error('ENOENT') as NodeJS.ErrnoException;
error.code = 'ENOENT';
throw error;
}
return data;
});
const atomicWrite = vi.fn(async (filePath: string, data: string) => {
if (dropWrites > 0) {
dropWrites -= 1;
files.set(norm(filePath), '[]');
return;
}
files.set(norm(filePath), data);
});
return {
files,
readFile,
atomicWrite,
nextId: () => `msg-${++idCounter}`,
resetCounter: () => {
idCounter = 0;
},
setDropWrites: (count: number) => {
dropWrites = count;
},
};
});
vi.mock('crypto', async (importOriginal) => {
const actual = await importOriginal<typeof import('crypto')>();
return {
...actual,
randomUUID: hoisted.nextId,
};
});
vi.mock('fs', () => ({
promises: {
readFile: hoisted.readFile,
},
}));
vi.mock('../../../../src/main/utils/pathDecoder', () => ({
getTeamsBasePath: () => '/mock/teams',
}));
vi.mock('../../../../src/main/services/team/atomicWrite', () => ({
atomicWriteAsync: hoisted.atomicWrite,
}));
import { TeamInboxWriter } from '../../../../src/main/services/team/TeamInboxWriter';
describe('TeamInboxWriter', () => {
const writer = new TeamInboxWriter();
const inboxPath = '/mock/teams/my-team/inboxes/alice.json';
beforeEach(() => {
hoisted.files.clear();
hoisted.readFile.mockClear();
hoisted.atomicWrite.mockClear();
hoisted.resetCounter();
hoisted.setDropWrites(0);
});
it('writes message with metadata and verifies messageId', async () => {
const result = await writer.sendMessage('my-team', {
member: 'alice',
text: 'hello',
summary: 'greeting',
});
const persisted = JSON.parse(hoisted.files.get(inboxPath) ?? '[]') as Record<string, unknown>[];
expect(result.deliveredToInbox).toBe(true);
expect(typeof result.messageId).toBe('string');
expect(persisted).toHaveLength(1);
expect(persisted[0]).toMatchObject({
from: 'user',
text: 'hello',
read: false,
summary: 'greeting',
messageId: result.messageId,
});
expect(typeof persisted[0].timestamp).toBe('string');
});
it('retries write when verify cannot find messageId', async () => {
hoisted.setDropWrites(2);
const result = await writer.sendMessage('my-team', {
member: 'alice',
text: 'retry me',
});
expect(typeof result.messageId).toBe('string');
expect(hoisted.atomicWrite).toHaveBeenCalledTimes(3);
});
it('throws after retries when verify keeps failing', async () => {
hoisted.setDropWrites(5);
await expect(
writer.sendMessage('my-team', {
member: 'alice',
text: 'will fail',
})
).rejects.toThrow('Failed to verify inbox write');
expect(hoisted.atomicWrite).toHaveBeenCalledTimes(3);
});
it('keeps both messages on parallel writes to same inbox', async () => {
await Promise.all([
writer.sendMessage('my-team', { member: 'alice', text: 'first' }),
writer.sendMessage('my-team', { member: 'alice', text: 'second' }),
]);
const persisted = JSON.parse(hoisted.files.get(inboxPath) ?? '[]') as { text: string }[];
expect(persisted).toHaveLength(2);
expect(persisted.map((row) => row.text).sort()).toEqual(['first', 'second']);
});
});