// Tests for server/audio.js — focused on the SSRF guard around
// downloadPodcastAudio (a fully user-controlled outbound fetch). The
// ffprobe/ffmpeg helpers need real media + binaries and aren't covered here.

import { test, describe } from "node:test";
import { strict as assert } from "node:assert";
import { isBlockedAddress, downloadPodcastAudio } from "../audio.js";

const SINK = "/tmp/recap-audio-test-should-not-be-written";

describe("isBlockedAddress", () => {
  test("blocks IPv4 loopback / private / link-local / reserved", () => {
    for (const ip of [
      "127.0.0.1", "127.1.2.3",
      "10.0.0.1", "172.16.0.1", "172.31.255.255", "192.168.1.1",
      "169.254.169.254", // cloud metadata
      "100.64.0.1", // CGNAT
      "0.0.0.0",
      "224.0.0.1", "255.255.255.255",
    ]) {
      assert.equal(isBlockedAddress(ip), true, `${ip} should be blocked`);
    }
  });

  test("allows ordinary public IPv4 (incl. 172.x boundaries)", () => {
    for (const ip of ["8.8.8.8", "1.1.1.1", "93.184.216.34", "172.15.0.1", "172.32.0.1"]) {
      assert.equal(isBlockedAddress(ip), false, `${ip} should be allowed`);
    }
  });

  test("blocks IPv6 loopback / ULA / link-local / multicast + IPv4-mapped privates", () => {
    for (const ip of [
      "::1", "::", "fc00::1", "fd12:3456::1", "fe80::1", "ff02::1",
      "::ffff:127.0.0.1", "::ffff:169.254.169.254",
    ]) {
      assert.equal(isBlockedAddress(ip), true, `${ip} should be blocked`);
    }
  });

  test("blocks hex-encoded embedded-IPv4 IPv6 forms (mapped/SIIT/NAT64/6to4)", () => {
    for (const ip of [
      "::ffff:7f00:1", // IPv4-mapped 127.0.0.1, hex form
      "::ffff:0:7f00:1", // SIIT 127.0.0.1
      "64:ff9b::7f00:1", // NAT64 well-known prefix of 127.0.0.1
      "2002:7f00:1::", // 6to4 of 127.0.0.1
    ]) {
      assert.equal(isBlockedAddress(ip), true, `${ip} should be blocked`);
    }
  });

  test("allows ordinary public IPv6", () => {
    assert.equal(isBlockedAddress("2606:4700:4700::1111"), false);
    assert.equal(isBlockedAddress("::ffff:8.8.8.8"), false);
  });

  test("blocks junk / empty / non-strings", () => {
    assert.equal(isBlockedAddress(""), true);
    assert.equal(isBlockedAddress(null), true);
    assert.equal(isBlockedAddress("not-an-ip"), true);
  });
});

describe("downloadPodcastAudio SSRF guard", () => {
  test("rejects non-HTTP(S) schemes", async () => {
    await assert.rejects(downloadPodcastAudio("file:///etc/passwd", SINK), /non-HTTP/);
    await assert.rejects(downloadPodcastAudio("ftp://example.com/x", SINK), /non-HTTP/);
  });

  test("rejects internal / private destinations before connecting", async () => {
    // 127.0.0.1:1 would refuse instantly if we connected; we must reject at
    // the DNS-guard step instead (proving the guard fires before connect).
    await assert.rejects(
      downloadPodcastAudio("http://127.0.0.1:1/x", SINK),
      /disallowed address/,
    );
    await assert.rejects(
      downloadPodcastAudio("http://169.254.169.254/latest/meta-data/", SINK),
      /disallowed address/,
    );
  });

  test("rejects a malformed URL", async () => {
    await assert.rejects(
      downloadPodcastAudio("not a url at all", SINK),
      /invalid podcast audio URL/,
    );
  });
});
