import { describe, expect, it } from 'vitest'; import { completeProjectSession, createProjectConversationState, normalizeProjectConversationState, patchProjectSessionMetadata, removeProjectSessionMetadata, upsertProjectSessionMetadata, } from '../../shared/project-conversations'; describe('project conversation metadata', () => { it('links multiple independent sessions to one contact and keeps them ordered by activity', () => { let state = createProjectConversationState('2026-07-30T00:00:00.000Z'); state = upsertProjectSessionMetadata(state, 'ses-a', 'agent-a', '2026-07-30T00:01:00.000Z'); state = upsertProjectSessionMetadata(state, 'ses-b', 'agent-a', '2026-07-30T00:02:00.000Z'); expect(state.sessions.map((session) => session.sessionId)).toEqual(['ses-b', 'ses-a']); expect(state.sessions.every((session) => session.agentId === 'agent-a')).toBe(true); }); it('archives one session without touching its sibling', () => { let state = createProjectConversationState(); state = upsertProjectSessionMetadata(state, 'ses-a', 'agent-a', '2026-07-30T00:01:00.000Z'); state = upsertProjectSessionMetadata(state, 'ses-b', 'agent-a', '2026-07-30T00:02:00.000Z'); state = patchProjectSessionMetadata(state, 'ses-a', { archivedAt: '2026-07-30T00:03:00.000Z' }, '2026-07-30T00:03:00.000Z'); expect(state.sessions.find((session) => session.sessionId === 'ses-a')?.archivedAt).toBe('2026-07-30T00:03:00.000Z'); expect(state.sessions.find((session) => session.sessionId === 'ses-b')?.archivedAt).toBeNull(); }); it('removes one session metadata record without touching its sibling', () => { let state = createProjectConversationState(); state = upsertProjectSessionMetadata(state, 'ses-a', 'agent-a', '2026-07-30T00:01:00.000Z'); state = upsertProjectSessionMetadata(state, 'ses-b', 'agent-a', '2026-07-30T00:02:00.000Z'); state = removeProjectSessionMetadata(state, 'ses-a', '2026-07-30T00:03:00.000Z'); expect(state.sessions.map((session) => session.sessionId)).toEqual(['ses-b']); expect(state.updatedAt).toBe('2026-07-30T00:03:00.000Z'); }); it('increments unread only for background completion and updates recent activity', () => { let state = createProjectConversationState(); state = upsertProjectSessionMetadata(state, 'ses-a', 'agent-a', '2026-07-30T00:01:00.000Z'); state = completeProjectSession(state, 'ses-a', true, '2026-07-30T00:04:00.000Z'); expect(state.sessions[0]).toMatchObject({ unreadCount: 1, updatedAt: '2026-07-30T00:04:00.000Z' }); state = completeProjectSession(state, 'ses-a', false, '2026-07-30T00:05:00.000Z'); expect(state.sessions[0]).toMatchObject({ unreadCount: 1, updatedAt: '2026-07-30T00:05:00.000Z' }); }); it('normalizes malformed persisted records and deduplicates by stable session id', () => { const normalized = normalizeProjectConversationState({ schemaVersion: 1, updatedAt: '2026-07-30T00:00:00.000Z', sessions: [ { sessionId: 'ses-a', agentId: 'agent-a', unreadCount: 2, createdAt: '', updatedAt: '' }, { sessionId: 'ses-a', agentId: 'agent-b', unreadCount: -3, createdAt: '2026-07-30T00:01:00.000Z', updatedAt: '2026-07-30T00:01:00.000Z' }, { sessionId: '', agentId: 'agent-a' }, ], }); expect(normalized.sessions).toHaveLength(1); expect(normalized.sessions[0]).toMatchObject({ sessionId: 'ses-a', agentId: 'agent-b', unreadCount: 0 }); }); });