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/.
This commit is contained in:
commit
aa5e262f3a
880 changed files with 111042 additions and 0 deletions
116
packages/connector-peertube/src/resolve.test.ts
Normal file
116
packages/connector-peertube/src/resolve.test.ts
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
canonicalPeerTubeHost,
|
||||
channelRemoteRef,
|
||||
PeerTubeResolveError,
|
||||
resolvePeerTubeSource,
|
||||
videoRemoteRef,
|
||||
} from "./resolve.js";
|
||||
|
||||
const INSTANCE = "https://video.example";
|
||||
const UUID = "0d1c2b3a-4e5f-4a6b-8c7d-9e0f1a2b3c4d";
|
||||
|
||||
function json(body: unknown, status = 200): Response {
|
||||
return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
||||
}
|
||||
|
||||
describe("remote_ref canonicalization", () => {
|
||||
it("IDNA-lowercases hosts in channel refs", () => {
|
||||
expect(channelRemoteRef("alice", "VIDEO.Example.")).toBe("channel:alice@video.example");
|
||||
expect(channelRemoteRef("Alice", "vïdeo.example")).toBe(channelRemoteRef("alice", "xn--vdeo-5pa.example"));
|
||||
});
|
||||
|
||||
it("lowercases uuids in video refs", () => {
|
||||
expect(videoRemoteRef(UUID.toUpperCase(), "video.example")).toBe(`video:${UUID}@video.example`);
|
||||
});
|
||||
|
||||
it("canonicalPeerTubeHost strips brackets and trailing dots", () => {
|
||||
expect(canonicalPeerTubeHost("[video.example].")).toBe("video.example");
|
||||
expect(canonicalPeerTubeHost("")).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolvePeerTubeSource", () => {
|
||||
it("resolves a channel through the public video-channels endpoint", async () => {
|
||||
const fetch = vi.fn(async (url: string) => {
|
||||
expect(url).toBe(`${INSTANCE}/api/v1/video-channels/alice%40video.example`);
|
||||
return json({ id: 7, name: "alice", host: "video.example", displayName: "Alice's channel" });
|
||||
});
|
||||
const result = await resolvePeerTubeSource("channel", "alice", "video.example", { fetch });
|
||||
expect(result).toMatchObject({
|
||||
remoteRef: "channel:alice@video.example",
|
||||
instanceUrl: INSTANCE,
|
||||
live: true,
|
||||
label: "Alice's channel",
|
||||
channelId: 7,
|
||||
});
|
||||
expect(fetch).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("resolves a running live: isLive + PUBLISHED, deterministic master.m3u8", async () => {
|
||||
const fetch = vi.fn(async () => json({ uuid: UUID, name: "My live", isLive: true, state: { id: 1, label: "Published" } }));
|
||||
const result = await resolvePeerTubeSource("live", UUID, "video.example", { fetch });
|
||||
expect(result.live).toBe(true);
|
||||
expect(result.playbackHlsUrl).toBe(`${INSTANCE}/static/streaming-playlists/hls/${UUID}/master.m3u8`);
|
||||
expect(result.remoteRef).toBe(`video:${UUID}@video.example`);
|
||||
});
|
||||
|
||||
it("a WAITING_FOR_LIVE video is not live and carries no playback URL", async () => {
|
||||
const fetch = vi.fn(async () => json({ uuid: UUID, name: "Waiting", isLive: true, state: { id: 4 } }));
|
||||
const result = await resolvePeerTubeSource("live", UUID, "video.example", { fetch });
|
||||
expect(result.live).toBe(false);
|
||||
expect(result.playbackHlsUrl).toBeUndefined();
|
||||
});
|
||||
|
||||
it("a VOD uses the API streamingPlaylists[].playlistUrl (type 1 only)", async () => {
|
||||
const fetch = vi.fn(async () => json({
|
||||
uuid: UUID, name: "VOD", isLive: false, state: { id: 1 },
|
||||
streamingPlaylists: [
|
||||
{ id: 1, type: 2, playlistUrl: "/dash/manifest.mpd" },
|
||||
{ id: 2, type: 1, playlistUrl: "/static/streaming-playlists/hls/x/master.m3u8" },
|
||||
],
|
||||
}));
|
||||
const result = await resolvePeerTubeSource("live", UUID, "video.example", { fetch });
|
||||
expect(result.live).toBe(false);
|
||||
expect(result.playbackHlsUrl).toBe(`${INSTANCE}/static/streaming-playlists/hls/x/master.m3u8`);
|
||||
});
|
||||
|
||||
it("404 maps to not_found (invalid row), 500 to upstream (failed row), 403 to denied", async () => {
|
||||
for (const [status, reason] of [[404, "not_found"], [410, "not_found"], [500, "upstream"], [403, "denied"]] as const) {
|
||||
const fetch = vi.fn(async () => json({ detail: "nope" }, status));
|
||||
await expect(resolvePeerTubeSource("channel", "alice", "video.example", { fetch }))
|
||||
.rejects.toMatchObject({ reason });
|
||||
await expect(resolvePeerTubeSource("live", UUID, "video.example", { fetch }))
|
||||
.rejects.toMatchObject({ reason });
|
||||
}
|
||||
});
|
||||
|
||||
it("transport failures are upstream, malformed payloads are upstream", async () => {
|
||||
const dead = vi.fn(async () => { throw new TypeError("socket hang up"); });
|
||||
await expect(resolvePeerTubeSource("channel", "alice", "video.example", { fetch: dead })).rejects.toMatchObject({ reason: "upstream" });
|
||||
const garbage = vi.fn(async () => new Response("<html>not peertube</html>", { status: 200 }));
|
||||
await expect(resolvePeerTubeSource("channel", "alice", "video.example", { fetch: garbage })).rejects.toBeInstanceOf(PeerTubeResolveError);
|
||||
const notVideo = vi.fn(async () => json({ name: "not a video" }));
|
||||
await expect(resolvePeerTubeSource("live", UUID, "video.example", { fetch: notVideo })).rejects.toMatchObject({ reason: "upstream" });
|
||||
});
|
||||
|
||||
it("forwards the abort signal to the transport", async () => {
|
||||
const fetch = vi.fn(async (_url: string, init?: RequestInit) => {
|
||||
expect((init as { signal?: AbortSignal } | undefined)?.signal?.aborted).toBe(true);
|
||||
return json({ uuid: UUID, isLive: false, name: "x", state: { id: 1 } });
|
||||
});
|
||||
const controller = new AbortController();
|
||||
controller.abort();
|
||||
await resolvePeerTubeSource("live", UUID, "video.example", { fetch, signal: controller.signal });
|
||||
expect(fetch).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("a shortUUID is passed through to the videos endpoint", async () => {
|
||||
const fetch = vi.fn(async (url: string) => {
|
||||
expect(url.endsWith("/api/v1/videos/abc123_XY")).toBe(true);
|
||||
return json({ uuid: UUID, isLive: false, name: "x", state: { id: 1 } });
|
||||
});
|
||||
const result = await resolvePeerTubeSource("live", "abc123_XY", "video.example", { fetch });
|
||||
expect(result.remoteRef).toBe(`video:${UUID}@video.example`);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue