123 lines · 5.3 KB
import { mock } from "bun:test";
import { memoryScopes } from "../scoped-db/testing";
import type { ScopedValue } from "../scoped-db/types";

/**
 * In-memory RepoState/AccountState stores and scope locators for tests: call `install()` before importing the
 * module under test, `reset()` in beforeEach, and seed rows with `repo(scope)` / `account(id)` (bun:sqlite).
 */
export function scopedTestState() {
  let scopes = memoryScopes();
  const locators = new Map<string, string>();
  const key = (kind: string, value: string) => `${kind}\u0000${value}`;
  const state = {
    install(): void {
      mock.module("../scoped-db", () => ({
        repoDb: (id: string) => scopes.repoDb(id),
        accountDb: (id: string) => scopes.accountDb(id),
        stmt: (sql: string, ...params: ScopedValue[]) => ({ sql, params }),
      }));
      mock.module("../scoped-db/locators", () => ({
        locate: async (kind: string, value: string) => locators.get(key(kind, value)) ?? null,
        registerLocator: async (kind: string, value: string, scopeId: string) => {
          locators.set(key(kind, value), scopeId);
        },
        registerLocators: async (entries: Array<[string, string, string]>) => {
          for (const [kind, value, scopeId] of entries) locators.set(key(kind, value), scopeId);
        },
        forgetLocator: async (kind: string, value: string) => {
          locators.delete(key(kind, value));
        },
        forgetScope: async (scopeId: string) => {
          for (const [entry, scope] of locators) if (scope === scopeId) locators.delete(entry);
        },
        locatedScopes: async (kind: string) => [
          ...new Set([...locators].filter(([entry]) => entry.startsWith(`${kind}\u0000`)).map(([, scope]) => scope)),
        ],
        locatedScopesFrom: async (kind: string, from: string, limit: number) =>
          [
            ...new Set(
              [...locators]