godcrm/backend/services/chat/__tests__/rowAttachment.test.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

97 lines
4 KiB
JavaScript

// ADR-0068 WP-B — rowAttachment.js is the single source of truth for the
// `bound_table_id = 0` sentinel ≡ "this conversation/message is bound to
// another conversation row". Tests pin the routing so future refactors can't
// accidentally re-route the sentinel through universal_tables.
import { describe, it, expect, beforeEach, vi } from 'vitest';
const dbGetMock = vi.fn();
vi.mock('../../../routes/v3/chat/chatShared.js', () => ({
dbGet: (...args) => dbGetMock(...args),
}));
const { resolveAttachedRow, isConversationSentinel, CONVERSATIONS_SENTINEL, BOUND_TABLE_ID_CONVERSATIONS, getCommentThreadChildId } = await import('../rowAttachment.js');
describe('rowAttachment — ADR-0068 WP-B sentinel resolver', () => {
beforeEach(() => dbGetMock.mockReset());
it('exports the sentinel constant as 0 (architect-locked value)', () => {
expect(CONVERSATIONS_SENTINEL).toBe(0);
expect(BOUND_TABLE_ID_CONVERSATIONS).toBe(0);
});
it('isConversationSentinel matches 0, rejects everything else', () => {
expect(isConversationSentinel(0)).toBe(true);
expect(isConversationSentinel('0')).toBe(true); // coerced
expect(isConversationSentinel(1)).toBe(false);
expect(isConversationSentinel(null)).toBe(false);
expect(isConversationSentinel(undefined)).toBe(false);
});
it('sentinel path queries conversations, not universal_tables', async () => {
dbGetMock.mockResolvedValueOnce({ id: 42, title: 'Parent chat', type: 'chat' });
const out = await resolveAttachedRow({ bound_table_id: 0, bound_row_id: 42 });
expect(out).toEqual({
table_name: 'conversations',
row_title: 'Parent chat',
source: 'conversation',
});
expect(dbGetMock).toHaveBeenCalledOnce();
expect(dbGetMock.mock.calls[0][0]).toMatch(/FROM\s+conversations/i);
expect(dbGetMock.mock.calls[0][0]).not.toMatch(/universal_tables/i);
});
it('falls back to row-id title when conversation has no title', async () => {
dbGetMock.mockResolvedValueOnce({ id: 42, title: null, type: 'chat' });
const out = await resolveAttachedRow({ bound_table_id: 0, bound_row_id: 42 });
expect(out.row_title).toBe('Conversation #42');
});
it('returns null when sentinel target conversation does not exist', async () => {
dbGetMock.mockResolvedValueOnce(null);
const out = await resolveAttachedRow({ bound_table_id: 0, bound_row_id: 999 });
expect(out).toBeNull();
});
it('non-sentinel path resolves via universal_tables', async () => {
dbGetMock.mockResolvedValueOnce({ name: 'tickets' });
const out = await resolveAttachedRow({ bound_table_id: 1708, bound_row_id: 158946 });
expect(out).toEqual({
table_name: 'tickets',
row_title: null,
source: 'universal_table',
});
expect(dbGetMock.mock.calls[0][0]).toMatch(/FROM\s+universal_tables/i);
});
it('returns null for invalid inputs', async () => {
expect(await resolveAttachedRow({ bound_table_id: 0, bound_row_id: 0 })).toBeNull();
expect(await resolveAttachedRow({ bound_table_id: null, bound_row_id: 42 })).toBeNull();
expect(await resolveAttachedRow({})).toBeNull();
});
it('getCommentThreadChildId returns the active child id', async () => {
dbGetMock.mockResolvedValueOnce({ id: 555 });
const out = await getCommentThreadChildId(100);
expect(out).toBe(555);
const [sql, params] = dbGetMock.mock.calls[0];
expect(sql).toMatch(/parent_conversation_id\s*=\s*\$1/);
expect(sql).toMatch(/purpose\s*=\s*'comments'/);
expect(params).toEqual([100]);
});
it('getCommentThreadChildId returns null when no active child exists', async () => {
dbGetMock.mockResolvedValueOnce(null);
expect(await getCommentThreadChildId(100)).toBeNull();
});
it('getCommentThreadChildId rejects invalid parent ids without hitting the DB', async () => {
expect(await getCommentThreadChildId(null)).toBeNull();
expect(await getCommentThreadChildId(0)).toBeNull();
expect(await getCommentThreadChildId(-5)).toBeNull();
expect(await getCommentThreadChildId('foo')).toBeNull();
expect(dbGetMock).not.toHaveBeenCalled();
});
});