/** * v1-parity tests for formatter behavior. * * Port of src/v1/formatting.test.ts (at commit 27c5220, parent of the v1 * deletion commit 86becf8). Covers: context timezone header, reply_to + * quoted_message rendering, XML escaping, and stripInternalTags. * * Timestamp-format assertions use `formatLocalTime()` output format, which * is host locale-dependent for decorators (month abbr, "," separator) but * stable for the numeric parts we assert on (hour, minute, year). */ import { describe, it, expect, beforeEach, afterEach } from 'bun:test'; import { initTestSessionDb, closeSessionDb, getInboundDb } from './db/connection.js'; import { getPendingMessages } from './db/messages-in.js'; import { formatMessages, stripInternalTags } from './formatter.js'; import { TIMEZONE } from './timezone.js'; beforeEach(() => { initTestSessionDb(); }); afterEach(() => { closeSessionDb(); }); function insertMessage( id: string, kind: string, content: object, opts?: { timestamp?: string }, ) { const timestamp = opts?.timestamp ?? new Date().toISOString(); getInboundDb() .prepare( `INSERT INTO messages_in (id, kind, timestamp, status, content) VALUES (?, ?, ?, 'pending', ?)`, ) .run(id, kind, timestamp, JSON.stringify(content)); } describe('context timezone header', () => { it('prepends to formatted output', () => { insertMessage('m1', 'chat', { sender: 'Alice', text: 'hello' }); const result = formatMessages(getPendingMessages()); expect(result).toContain(` { const result = formatMessages([]); expect(result).toContain(` block', () => { insertMessage('m1', 'chat', { sender: 'Alice', text: 'one' }); insertMessage('m2', 'chat', { sender: 'Bob', text: 'two' }); const result = formatMessages(getPendingMessages()); const ctxIdx = result.indexOf(''); expect(ctxIdx).toBeGreaterThanOrEqual(0); expect(msgsIdx).toBeGreaterThan(ctxIdx); }); }); describe('timestamp formatting', () => { it('renders time via formatLocalTime (user TZ)', () => { // 2026-06-15T12:00:00Z — timezone-agnostic assertions (year is stable) insertMessage('m1', 'chat', { sender: 'Alice', text: 'hi' }, { timestamp: '2026-06-15T12:00:00.000Z' }); const result = formatMessages(getPendingMessages()); // formatLocalTime's format in en-US contains the year and a month abbrev expect(result).toContain('2026'); expect(result).toMatch(/Jun/); }); it('uses 12-hour AM/PM format', () => { // 15:30 UTC — some hour will show with AM or PM depending on TZ insertMessage('m1', 'chat', { sender: 'Alice', text: 'hi' }, { timestamp: '2026-06-15T15:30:00.000Z' }); const result = formatMessages(getPendingMessages()); expect(result).toMatch(/(AM|PM)/); }); }); describe('reply_to + quoted_message rendering', () => { it('renders reply_to attribute and quoted_message when all fields present', () => { insertMessage('m1', 'chat', { sender: 'Alice', text: 'Yes, on my way!', replyTo: { id: '42', sender: 'Bob', text: 'Are you coming tonight?' }, }); const result = formatMessages(getPendingMessages()); expect(result).toContain('reply_to="42"'); expect(result).toContain('Are you coming tonight?'); expect(result).toContain('Yes, on my way!'); }); it('omits reply_to and quoted_message when no reply context', () => { insertMessage('m1', 'chat', { sender: 'Alice', text: 'plain' }); const result = formatMessages(getPendingMessages()); expect(result).not.toContain('reply_to'); expect(result).not.toContain('quoted_message'); }); it('renders reply_to but omits quoted_message when original content is missing', () => { insertMessage('m1', 'chat', { sender: 'Alice', text: 'ack', replyTo: { id: '42', sender: 'Bob' }, // no text }); const result = formatMessages(getPendingMessages()); expect(result).toContain('reply_to="42"'); expect(result).not.toContain('quoted_message'); }); it('XML-escapes reply context', () => { insertMessage('m1', 'chat', { sender: 'Alice', text: 'reply', replyTo: { id: '1', sender: 'A & B', text: '' }, }); const result = formatMessages(getPendingMessages()); expect(result).toContain('from="A & B"'); expect(result).toContain('<script>'); expect(result).toContain('"xss"'); }); }); describe('XML escaping', () => { it('escapes <, >, &, " in sender and body', () => { insertMessage('m1', 'chat', { sender: 'A & B ', text: '', }); const result = formatMessages(getPendingMessages()); expect(result).toContain('sender="A & B <Co>"'); expect(result).toContain('<script>alert("xss")</script>'); }); }); describe('stripInternalTags', () => { it('strips single-line internal tags and trims', () => { expect(stripInternalTags('hello secret world')).toBe('hello world'); }); it('strips multi-line internal tags', () => { expect(stripInternalTags('hello \nsecret\nstuff\n world')).toBe( 'hello world', ); }); it('strips multiple internal tag blocks', () => { expect(stripInternalTags('ahellob')).toBe('hello'); }); it('returns empty string when input is only internal tags', () => { expect(stripInternalTags('only this')).toBe(''); }); it('returns input unchanged when there are no internal tags', () => { expect(stripInternalTags('hello world')).toBe('hello world'); }); it('preserves content that surrounds internal tags', () => { expect(stripInternalTags('thinkingThe answer is 42')).toBe( 'The answer is 42', ); }); });