branch:
sfu-integration.test.ts
3656 bytesRaw
/**
 * Integration tests for the Cloudflare Realtime SFU API.
 *
 * These tests call the real SFU API at rtc.live.cloudflare.com and require:
 *   CLOUDFLARE_REALTIME_SFU_APP_ID
 *   CLOUDFLARE_REALTIME_SFU_API_TOKEN
 *
 * Excluded from the Workers pool test suite (runs via plain vitest).
 * Run with: npx vitest run src/tests/sfu-integration.test.ts
 * Skip with: SKIP_SFU_INTEGRATION=1 npx vitest run src/tests/sfu-integration.test.ts
 */
import { describe, expect, it, beforeAll } from "vitest";
import {
  createSFUSession,
  addSFUTracks,
  createSFUWebSocketAdapter,
  type SFUConfig
} from "../sfu-utils";

let config: SFUConfig;

const shouldSkip =
  !process.env.CLOUDFLARE_REALTIME_SFU_APP_ID ||
  !process.env.CLOUDFLARE_REALTIME_SFU_API_TOKEN ||
  process.env.SKIP_SFU_INTEGRATION === "1";

beforeAll(() => {
  if (shouldSkip) return;
  config = {
    appId: process.env.CLOUDFLARE_REALTIME_SFU_APP_ID!,
    apiToken: process.env.CLOUDFLARE_REALTIME_SFU_API_TOKEN!
  };
});

describe.skipIf(shouldSkip)("SFU API — session lifecycle", () => {
  it("creates a new session", async () => {
    const result = await createSFUSession(config);
    expect(result).toHaveProperty("sessionId");
    expect(typeof result.sessionId).toBe("string");
    expect(result.sessionId.length).toBeGreaterThan(0);
  });

  it("creates multiple independent sessions", async () => {
    const [s1, s2] = await Promise.all([
      createSFUSession(config),
      createSFUSession(config)
    ]);

    expect(s1.sessionId).not.toBe(s2.sessionId);
  });
});

describe.skipIf(shouldSkip)("SFU API — tracks", () => {
  it("rejects addTracks with invalid sessionId", async () => {
    await expect(
      addSFUTracks(config, "nonexistent-session-id", {
        sessionDescription: {
          type: "offer",
          sdp: "v=0\r\n"
        },
        tracks: []
      })
    ).rejects.toThrow(/SFU API error/);
  });

  it("rejects addTracks with malformed SDP on a valid session", async () => {
    const session = await createSFUSession(config);

    // The SFU should reject a nonsense SDP
    await expect(
      addSFUTracks(config, session.sessionId, {
        sessionDescription: {
          type: "offer",
          sdp: "this is not valid SDP"
        },
        tracks: [
          {
            location: "local",
            trackName: "test-audio",
            mid: "0"
          }
        ]
      })
    ).rejects.toThrow(/SFU API error/);
  });
});

describe.skipIf(shouldSkip)("SFU API — WebSocket adapter", () => {
  it("rejects adapter creation with no tracks", async () => {
    // The SFU requires at least one track reference
    await expect(createSFUWebSocketAdapter(config, [])).rejects.toThrow(
      /SFU API error/
    );
  });

  it("rejects adapter creation with invalid track references", async () => {
    await expect(
      createSFUWebSocketAdapter(config, [
        {
          location: "remote",
          sessionId: "nonexistent",
          trackName: "nonexistent"
        }
      ])
    ).rejects.toThrow(/SFU API error/);
  });
});

describe.skipIf(shouldSkip)("SFU API — auth", () => {
  it("rejects requests with invalid token", async () => {
    const badConfig: SFUConfig = {
      appId: config.appId,
      apiToken: "invalid-token-12345"
    };

    await expect(createSFUSession(badConfig)).rejects.toThrow(
      /SFU API error (401|403)/
    );
  });

  it("rejects requests with invalid app ID", async () => {
    const badConfig: SFUConfig = {
      appId: "nonexistent-app-id",
      apiToken: config.apiToken
    };

    await expect(createSFUSession(badConfig)).rejects.toThrow(/SFU API error/);
  });
});