Approved posts may carry mediaAssetIds and altTexts since the repository gate was lifted, but the scheduler built connectors without an asset loader, so Mastodon and Telegram rejected every post with media as INVALID_POST and alt texts never reached them. - media.ts: loadAsset reads media_assets inside the execution's tenant (app.workspace_id, RLS forced); openAsset fetches the sealed object, opens the SPENC1 frame with S3_KEK and checks the content hash. Read-only; bytes go worker to provider and never through the API. - preparePostForDispatch maps positional altTexts onto providerMetadata.altText keyed by asset id. - Instagram joins the scheduler registry so its posts fail with an explicit connector error instead of UnknownProviderError. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
28 lines
2.1 KiB
TypeScript
28 lines
2.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { Pool } from "pg";
|
|
import { createPublishingRegistry, verifiedDedupeOrigin } from "./registry.js";
|
|
import { createPublishingCredentialAccessor } from "./credentials.js";
|
|
import type { ConnectorConnection } from "@tactical-media-broadcast-hub/core";
|
|
import { runWithPublishingContext } from "./execution-context.js";
|
|
const connection: ConnectorConnection = { id: "connection", workspaceId: "workspace", provider: "mastodon", channelId: "channel", fields: {}, createdAt: new Date(), credentialId: { kind: "credential-ref", id: "connection" } };
|
|
describe("production publishing registry", () => {
|
|
it("registers real Mastodon, Telegram and Instagram connectors and rejects duplicates", () => {
|
|
const pool = {} as Pool;
|
|
const registry = createPublishingRegistry(pool);
|
|
expect(registry.list().map((c) => c.manifest().id).sort()).toEqual(["instagram", "mastodon", "telegram"]);
|
|
expect(registry.get("mastodon").publish).toBeTypeOf("function");
|
|
expect(() => registry.register(registry.get("mastodon"))).toThrow(/already registered/);
|
|
});
|
|
it("trusts only explicitly configured HTTPS origins", () => {
|
|
expect(verifiedDedupeOrigin("https://social.test", "")).toBe(false);
|
|
expect(verifiedDedupeOrigin("https://social.test", "https://social.test")).toBe(true);
|
|
expect(verifiedDedupeOrigin("https://social.test", "https://social.test/path")).toBe(false);
|
|
expect(verifiedDedupeOrigin("http://social.test", "http://social.test")).toBe(false);
|
|
});
|
|
it("refuses absent or mismatched context and publishing credential mutation before SQL", async () => {
|
|
const accessor = createPublishingCredentialAccessor({} as Pool);
|
|
await expect(accessor.getAccessToken(connection)).rejects.toThrow("EXECUTION_CREDENTIAL_FENCE");
|
|
await expect(accessor.put(connection, "access_token", "secret")).rejects.toThrow("MUTATION_REFUSED");
|
|
await expect(runWithPublishingContext({ workspaceId: "other", connectionId: "connection", credentialId: "uuid", credentialGeneration: "1" }, () => accessor.getAccessToken(connection))).rejects.toThrow("EXECUTION_CREDENTIAL_FENCE");
|
|
});
|
|
});
|