144 lines
4.5 KiB
TypeScript
144 lines
4.5 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import { TeamMessageFeedService } from '../../../../src/main/services/team/TeamMessageFeedService';
|
|
|
|
import type { InboxMessage, TeamConfig } from '../../../../src/shared/types/team';
|
|
|
|
function makeMessage(overrides: Partial<InboxMessage> = {}): InboxMessage {
|
|
return {
|
|
from: 'user',
|
|
to: 'jack',
|
|
text: 'Тут?',
|
|
timestamp: '2026-04-19T18:46:37.613Z',
|
|
read: true,
|
|
source: 'user_sent',
|
|
messageId: 'user-send-1',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('TeamMessageFeedService', () => {
|
|
const config: TeamConfig = {
|
|
name: 'Signal Ops 4',
|
|
members: [{ name: 'team-lead', role: 'Lead' }],
|
|
};
|
|
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
vi.setSystemTime(new Date('2026-04-19T18:46:40.000Z'));
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it('reuses the cached feed within the cache TTL when no dirty invalidation arrives', async () => {
|
|
let inboxMessages: InboxMessage[] = [makeMessage()];
|
|
const getInboxMessages = vi.fn(async () => inboxMessages);
|
|
const service = new TeamMessageFeedService({
|
|
getConfig: vi.fn(async () => config),
|
|
getInboxMessages,
|
|
getLeadSessionMessages: vi.fn(async () => []),
|
|
getSentMessages: vi.fn(async () => []),
|
|
});
|
|
|
|
const first = await service.getFeed('signal-ops-4');
|
|
expect(first.messages).toHaveLength(1);
|
|
|
|
inboxMessages = [
|
|
makeMessage({
|
|
from: 'jack',
|
|
to: 'user',
|
|
text: 'Да, я тут, на связи. Что нужно сделать/проверить?',
|
|
source: 'inbox',
|
|
timestamp: '2026-04-19T18:46:43.427Z',
|
|
}),
|
|
...inboxMessages,
|
|
];
|
|
|
|
vi.setSystemTime(new Date('2026-04-19T18:46:43.000Z'));
|
|
|
|
const second = await service.getFeed('signal-ops-4');
|
|
expect(getInboxMessages).toHaveBeenCalledTimes(1);
|
|
expect(second.messages).toHaveLength(1);
|
|
});
|
|
|
|
it('refreshes the durable feed after cache expiry even when the dirty signal was missed', async () => {
|
|
let inboxMessages: InboxMessage[] = [makeMessage()];
|
|
const getInboxMessages = vi.fn(async () => inboxMessages);
|
|
const service = new TeamMessageFeedService({
|
|
getConfig: vi.fn(async () => config),
|
|
getInboxMessages,
|
|
getLeadSessionMessages: vi.fn(async () => []),
|
|
getSentMessages: vi.fn(async () => []),
|
|
});
|
|
|
|
await service.getFeed('signal-ops-4');
|
|
|
|
inboxMessages = [
|
|
makeMessage({
|
|
from: 'jack',
|
|
to: 'user',
|
|
text: 'Да, я тут, на связи. Что нужно сделать/проверить?',
|
|
source: 'inbox',
|
|
timestamp: '2026-04-19T18:46:43.427Z',
|
|
}),
|
|
makeMessage(),
|
|
];
|
|
|
|
vi.setSystemTime(new Date('2026-04-19T18:46:46.500Z'));
|
|
|
|
const refreshed = await service.getFeed('signal-ops-4');
|
|
expect(getInboxMessages).toHaveBeenCalledTimes(2);
|
|
expect(
|
|
refreshed.messages.some(
|
|
(message) =>
|
|
message.from === 'jack' &&
|
|
message.to === 'user' &&
|
|
message.text.includes('Да, я тут')
|
|
)
|
|
).toBe(true);
|
|
});
|
|
|
|
it('adds UI-only OpenCode bootstrap start rows for side-lane teammates', async () => {
|
|
const opencodeConfig: TeamConfig = {
|
|
name: 'relay-works-14',
|
|
description: 'relay-works-14 team for provisioning flow',
|
|
members: [
|
|
{ name: 'team-lead', role: 'Lead', providerId: 'codex' },
|
|
{
|
|
name: 'bob',
|
|
role: 'developer',
|
|
providerId: 'opencode',
|
|
model: 'openrouter/moonshotai/kimi-k2.6',
|
|
joinedAt: 1777570946947,
|
|
},
|
|
],
|
|
};
|
|
const service = new TeamMessageFeedService({
|
|
getConfig: vi.fn(async () => opencodeConfig),
|
|
getInboxMessages: vi.fn(async () => []),
|
|
getLeadSessionMessages: vi.fn(async () => []),
|
|
getSentMessages: vi.fn(async () => []),
|
|
});
|
|
|
|
const feed = await service.getFeed('relay-works-14');
|
|
|
|
expect(feed.messages).toHaveLength(1);
|
|
expect(feed.messages[0]).toMatchObject({
|
|
from: 'team-lead',
|
|
to: 'bob',
|
|
source: 'system_notification',
|
|
messageId: 'opencode-bootstrap-start:relay-works-14:bob',
|
|
timestamp: '2026-04-30T17:42:26.947Z',
|
|
});
|
|
expect(feed.messages[0]?.text).toContain('Provider override for this teammate: opencode.');
|
|
expect(feed.messages[0]?.text).toContain(
|
|
'Model override for this teammate: openrouter/moonshotai/kimi-k2.6.'
|
|
);
|
|
expect(feed.messages[0]?.text).toContain(
|
|
'The team has already been created and you are being attached as a persistent teammate.'
|
|
);
|
|
});
|
|
});
|