T-HUB/apps/api/src/connections/connection-store.test.ts
glass beadian aa5e262f3a Tactical Media Broadcast Hub — initial release
Self-hosted multi-platform broadcast newsroom for activist collectives:
publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style
column wall with hostile-content-safe rendering, live control room with Auto
Director, bridged chat governance with polls and quorum, distributed sandboxed
transcode workers, encrypted content-addressed archive, monthly analytics.

MIT licensed. See README.md and docs/.
2026-10-10 11:56:42 +00:00

76 lines
5.4 KiB
TypeScript

import { randomUUID } from "node:crypto";
import type { PoolClient } from "pg";
import { describe, expect, it } from "vitest";
import type { ConnectorConnection } from "@tactical-media-broadcast-hub/core";
import type { SessionPayload } from "../auth/session.js";
import { Vault } from "../vault/vault.js";
import { PgConnectionStore } from "./connection-store.js";
const session: SessionPayload = { workspaceId: randomUUID(), userId: randomUUID(), role: "Owner", authzEpoch: 3, issuedAt: 0, expiresAt: Math.floor(Date.now() / 1000) + 600 };
const connection: ConnectorConnection = { id: randomUUID(), workspaceId: session.workspaceId, provider: "mastodon", channelId: "remote-account", credentialId: { kind: "credential-ref", id: "opaque" }, fields: { instanceUrl: "https://social.example" }, createdAt: new Date() };
function fixture(allowed = true, existingCredential = false) {
const queries: Array<{ sql: string; params: unknown[] }> = [];
const credentialId = randomUUID();
const client = {
async query(sql: string, params: unknown[] = []) {
queries.push({ sql, params });
if (sql.includes("FROM memberships")) return { rowCount: allowed ? 1 : 0, rows: [] };
if (sql.startsWith("UPDATE scheduled_publications")) return { rowCount: 1, rows: [{ id: connection.id, draft_id: connection.id }] };
if (sql.includes("SELECT id FROM credentials")) return { rows: existingCredential ? [{ id: credentialId }] : [] };
if (sql.includes("SELECT c.*, cr.ciphertext")) return { rows: [{ id: connection.id, provider: connection.provider, channel_id: connection.channelId, fields: connection.fields, created_at: connection.createdAt, ciphertext: null, key_version: 1 }] };
return { rowCount: 1, rows: [{ id: connection.id }] };
},
} as unknown as PoolClient;
const store = new PgConnectionStore(async (workspaceId, callback) => {
expect(workspaceId).toBe(session.workspaceId);
return callback(client);
});
return { store, queries, credentialId };
}
describe("Postgres connection writes", () => {
it("saves only a vault envelope and identifier-only audit in the same tenant transaction", async () => {
const { store, queries } = fixture();
const sealed = await new Vault().seal(session.workspaceId, connection.id, Buffer.from('{"access_token":"never-in-db-plaintext"}'));
await store.save(session, connection, { provider: connection.provider, remoteId: "remote-account", handle: "@alice" }, sealed, false);
const credential = queries.find(({ sql }) => sql.startsWith("INSERT INTO credentials"));
expect(credential?.params).toContain(sealed.ciphertext);
expect(queries[0]?.sql).toContain("FOR UPDATE");
expect(JSON.stringify(queries)).not.toContain("never-in-db-plaintext");
const audit = queries.find(({ sql }) => sql.includes("audit_append"));
expect(audit?.params.at(-1)).toEqual({ provider: connection.provider });
});
it("rotates the credential row in place and blocks a revoked authorization before writes", async () => {
const { store, queries, credentialId } = fixture(true, true);
const sealed = await new Vault().seal(session.workspaceId, connection.id, Buffer.from("rotated-secret"));
await store.save(session, connection, { provider: connection.provider, remoteId: "remote-account", handle: "@alice" }, sealed, true);
expect(queries.find(({ sql }) => sql.startsWith("UPDATE credentials"))?.params[0]).toBe(credentialId);
expect(queries.some(({ sql }) => sql.startsWith("INSERT INTO connections"))).toBe(false);
const denied = fixture(false);
await expect(denied.store.save(session, connection, { provider: connection.provider, remoteId: "remote-account", handle: "@alice" }, sealed, true)).rejects.toThrow();
expect(denied.queries.some(({ sql }) => /^(INSERT|UPDATE)/.test(sql))).toBe(false);
});
it("locks and revokes locally before provider I/O while preserving publishing history", async () => {
const { store, queries } = fixture();
await store.remove(session, connection.id, async (stored) => {
expect(stored.connection.id).toBe(connection.id);
expect(queries.some(({ sql }) => sql.includes("FOR UPDATE"))).toBe(true);
expect(queries.some(({ sql }) => sql.startsWith("DELETE"))).toBe(false);
});
expect(queries.filter(({ sql }) => sql.startsWith("DELETE"))).toHaveLength(0);
expect(queries.some(({ sql }) => sql.startsWith("UPDATE credentials SET revoked_at"))).toBe(true);
expect(queries.some(({ sql }) => sql.startsWith("UPDATE connections SET disconnected_at"))).toBe(true);
expect(queries.some(({ sql }) => sql.includes("needs_reauthorization"))).toBe(true);
expect(queries.some(({ sql }) => sql.startsWith("UPDATE publishing_attempts") && sql.includes("abandoned"))).toBe(true);
expect(queries.some(({ sql }) => sql.startsWith("UPDATE drafts") && sql.includes("approved_revision = revision"))).toBe(true);
expect(queries.at(-1)?.sql).toContain("audit_append");
});
it("retains revoked local evidence and audits provider revocation failure", async () => {
const { store, queries } = fixture();
await expect(store.remove(session, connection.id, async () => { throw new Error("provider offline"); })).resolves.toBeUndefined();
expect(queries.some(({ sql }) => sql.startsWith("UPDATE credentials SET revoked_at"))).toBe(true);
expect(queries.at(-1)?.sql).toContain("connection.revoke_failed");
expect(queries.some(({ sql }) => sql.startsWith("DELETE"))).toBe(false);
});
});