Files
suaveplan/packages/communication/messaging/imessage-adapter/src/fixtures/chat-db-fixture.md
2026-05-27 23:29:53 +01:00

3.5 KiB

createChatDbFixture

(no submodules)

Purpose

Builds a real chat.db-shaped SQLite database on a temp path using the structural SqliteReader seam. This is the foundation of the test strategy: every watcher / adapter test runs against a real bun:sqlite database whose schema mirrors Apple's Messages app, populated via the helpers returned by this factory. There are no mocks anywhere in the call chain — the SQL the watcher executes in production runs unchanged against the fixture.

Features

  • DDL covers the six tables the watcher reads: message, handle, chat, chat_message_join, attachment, message_attachment_join — with the exact column names + types Apple's chat.db uses.
  • Helpers return the inserted ROWID via a SELECT ROWID ORDER BY ROWID DESC LIMIT 1 so tests can wire foreign-key joins deterministically without depending on last_insert_rowid() semantics across reader implementations.
  • insertMessage(row, chatId) writes both the message and its chat_message_join row so the watcher's JOIN cmj query finds the chat without extra wiring.
  • insertAttachment(row, messageId) writes both the attachment and its message_attachment_join row.
  • close() closes the handle so the temp file can be deleted by the caller's afterEach.

Usage: Basic

import { tmpdir } from "node:os";
import { join } from "node:path";
import {
    createBunSqliteReader,
    createChatDbFixture,
} from "@suaveplan/imessage-adapter";

const reader = createBunSqliteReader();
const fixture = createChatDbFixture(reader, join(tmpdir(), "chat.db"));
const chatId = fixture.insertChat({
    guid: "iMessage;+;+15551234567",
    chat_identifier: "+15551234567",
    display_name: null,
    group_id: null,
    service_name: "iMessage",
    is_archived: 0,
});
fixture.close();

Usage: Advanced

const handleId = fixture.insertHandle({
    id: "+15551234567",
    country: "us",
    service: "iMessage",
    uncanonicalized_id: null,
});
const messageId = fixture.insertMessage({
    guid: "m-1",
    text: "Hello",
    handle_id: handleId,
    service: "iMessage",
    date: 700_000_000,
    date_read: 0,
    date_delivered: 0,
    is_from_me: 0,
    is_read: 1,
    is_delivered: 1,
    is_sent: 0,
    cache_has_attachments: 1,
    associated_message_guid: null,
    associated_message_type: 0,
    balloon_bundle_id: null,
    group_title: null,
}, chatId);
fixture.insertAttachment({
    guid: "a-1",
    filename: "/tmp/photo.jpg",
    mime_type: "image/jpeg",
    total_bytes: 1024,
    transfer_name: "photo.jpg",
}, messageId);

API Reference

  • createChatDbFixture(reader, path): ChatDbFixture — DDLs the schema and returns the helpers.
  • ChatDbFixture.handle: SqliteHandle — exposed for advanced custom inserts.
  • insertHandle(row): number — returns the new ROWID.
  • insertChat(row): number — returns the new ROWID.
  • insertMessage(row, chatId?): number — wires the chat_message_join row when chatId is supplied.
  • insertAttachment(row, messageId): number — wires the message_attachment_join row.
  • close(): void — closes the handle.

Implementation Notes

The factory is server-tier-only because it requires a real SQLite implementation, but it accepts any SqliteReader so a future in-process SQLite-WASM reader could also drive the fixture. The schema uses AUTOINCREMENT so insertion order maps to monotonically increasing ROWIDs, which the watcher's ROWID > lastRowId cursor relies on for incremental polling.