/** * Deterministic social mock: publish (text/images/carousel), feed, comments, * replies, webhooks. In-memory state — CI runs need no network. */ import type { Comment, ConnectorCapabilities, ConnectorConnection, FeedItem, NormalizedIdentity, NormalizedPost, NormalizedWebhookEvent, Page, Publication, } from "@tactical-media-broadcast-hub/core"; import { assertValidManifest, pageOf, type ConnectorManifest, type PlatformConnector, } from "@tactical-media-broadcast-hub/connector-sdk"; const CAPABILITIES: ConnectorCapabilities = { publishText: true, publishImages: true, publishCarousel: true, feed: true, mentions: true, comments: true, replies: true, amplifyRepost: true, amplifyQuote: true, webhooks: true, deletePublication: true, }; let seq = 0; const nextId = (prefix: string): string => `${prefix}-${(seq += 1)}`; export interface MockSocialState { publications: Map; feed: FeedItem[]; comments: Map; } export function createMockSocialState(): MockSocialState { return { publications: new Map(), feed: [], comments: new Map() }; } export function createMockSocialConnector( state: MockSocialState = createMockSocialState(), ): PlatformConnector { const manifest: ConnectorManifest = { id: "mock-social", displayName: "Mock Social", capabilities: CAPABILITIES, connectionFields: [{ name: "apiToken", label: "API token", required: true, secret: true }], webhookRoute: "/webhooks/mock-social", }; return { manifest: () => { assertValidManifest(manifest); return manifest; }, async authorize() { throw new Error("mock-social uses token auth in tests only"); }, async getIdentity(connection: ConnectorConnection): Promise { return { provider: manifest.id, remoteId: connection.channelId, handle: "mock@social", displayName: "Mock Social Account", }; }, async publish(connection: ConnectorConnection, post: NormalizedPost): Promise { const publication: Publication = { id: nextId("pub"), workspaceId: connection.workspaceId, draftId: "direct", connectionId: connection.id, remoteId: nextId("remote"), publishedAt: new Date(), providerMetadata: post.providerMetadata, }; state.publications.set(publication.remoteId, publication); state.feed.unshift({ id: publication.remoteId, provider: manifest.id, remoteId: publication.remoteId, authorHandle: "mock@social", text: post.text, mediaUrls: [], createdAt: publication.publishedAt, metrics: {}, }); return publication; }, async deletePublication(_connection: ConnectorConnection, remoteId: string): Promise { state.publications.delete(remoteId); state.feed = state.feed.filter((item) => item.remoteId !== remoteId); }, async getFeed( _connection: ConnectorConnection, request: { kind: "home" | "mentions" | "search"; query?: string; cursor?: string; limit: number }, ): Promise> { const filtered = request.kind === "search" && request.query ? state.feed.filter((item) => item.text.includes(request.query ?? "")) : state.feed; const start = request.cursor ? Number(request.cursor) : 0; const items = filtered.slice(start, start + request.limit); const next = start + request.limit; return next < filtered.length ? pageOf(items, String(next)) : pageOf(items); }, async getComments( _connection: ConnectorConnection, request: { onRemoteId: string; cursor?: string; limit: number }, ): Promise> { const all = state.comments.get(request.onRemoteId) ?? []; return pageOf(all.slice(0, request.limit)); }, async reply( connection: ConnectorConnection, request: { onRemoteId: string; text: string }, ): Promise { const existing = state.comments.get(request.onRemoteId) ?? []; const comment: Comment = { id: nextId("comment"), provider: manifest.id, remoteId: nextId("cremote"), onRemoteId: request.onRemoteId, authorHandle: "mock@social", text: request.text, createdAt: new Date(), }; existing.push(comment); state.comments.set(request.onRemoteId, existing); return { id: comment.id, workspaceId: connection.workspaceId, draftId: "reply", connectionId: connection.id, remoteId: comment.remoteId, publishedAt: comment.createdAt, providerMetadata: {}, }; }, async amplify( connection: ConnectorConnection, request: { kind: "repost" | "quote" | "share"; targetRemoteId: string; commentary?: string }, ): Promise { const target = state.feed.find((item) => item.remoteId === request.targetRemoteId); if (target === undefined) throw new Error(`no item ${request.targetRemoteId} to amplify`); const amplified: Publication = { id: nextId("amp"), workspaceId: connection.workspaceId, draftId: `amplify:${request.kind}`, connectionId: connection.id, remoteId: nextId("remote"), publishedAt: new Date(), providerMetadata: { amplifies: request.targetRemoteId, kind: request.kind }, }; state.feed.unshift({ id: amplified.remoteId, provider: manifest.id, remoteId: amplified.remoteId, authorHandle: "mock@social", text: request.kind === "quote" ? `${request.commentary ?? ""} ↻ ${target.text}` : `↻ ${target.text}`, mediaUrls: [], createdAt: amplified.publishedAt, metrics: {}, }); return amplified; }, async subscribeWebhooks() { return { subscriptionId: nextId("hook") }; }, async handleWebhook(request: { headers: Record; body: unknown }): Promise { return { provider: manifest.id, eventType: "mock.event", payload: (request.body as Record) ?? {}, receivedAt: new Date(), }; }, }; }