// Release Beta Smoke tests cover release beta smoke script behavior.
import { describe, expect, it } from "vitest";
import {
  mergeTelegramProofIntoReleaseBody,
  parseArgs,
  parseWorkflowRunIdFromOutput,
  pollRun,
  readPositiveInt,
  requireWorkflowRunIdFromOutput,
  run,
} from "../../scripts/release-beta-smoke.ts";

describe("release-beta-smoke", () => {
  it("rejects runs with both validation lanes skipped", () => {
    expect(() => parseArgs(["--skip-parallels", "--skip-telegram"])).toThrow(
      "--skip-parallels and --skip-telegram cannot be used together",
    );
  });

  it("stops parsing options after the argument terminator", () => {
    expect(
      parseArgs(["--beta", "beta-a", "--", "--skip-parallels", "--skip-telegram"]),
    ).toMatchObject({
      beta: "beta-a",
      skipParallels: false,
      skipTelegram: false,
    });
  });

  it("accepts package-manager argument separators before script options", () => {
    expect(parseArgs(["--", "--beta", "beta-a", "--skip-parallels"])).toMatchObject({
      beta: "beta-a",
      skipParallels: true,
    });
  });

  it("rejects malformed positive integer environment limits", () => {
    expect(readPositiveInt(undefined, 60, "OPENCLAW_RELEASE_BETA_SMOKE_COMMAND_MS")).toBe(60);
    expect(readPositiveInt("", 60, "OPENCLAW_RELEASE_BETA_SMOKE_COMMAND_MS")).toBe(60);
    expect(readPositiveInt("25", 60, "OPENCLAW_RELEASE_BETA_SMOKE_COMMAND_MS")).toBe(25);

    for (const raw of ["1e3", "25ms", "1.5", "0", "-1", String(Number.MAX_SAFE_INTEGER + 1)]) {
      expect(() => readPositiveInt(raw, 60, "OPENCLAW_RELEASE_BETA_SMOKE_COMMAND_MS")).toThrow(
        "OPENCLAW_RELEASE_BETA_SMOKE_COMMAND_MS must be a positive integer",
      );
    }
  });

  it("parses workflow run urls when gh includes them in dispatch output", () => {
    expect(
      parseWorkflowRunIdFromOutput(
        "Dispatched: https://github.com/openclaw/openclaw/actions/runs/1234567890",
      ),
    ).toBe("1234567890");
  });

  it("fails closed when gh dispatch output does not include the run url", () => {
    expect(() =>
      requireWorkflowRunIdFromOutput("✓ Created workflow_dispatch event", "npm-telegram.yml"),
    ).toThrow("refusing to guess from recent workflow_dispatch runs");
  });

  it("replaces stale Telegram proof placeholders", () => {
    const body = [
      "## Changes",
      "",
      "### Release verification",
      "",
      "- npm package: https://www.npmjs.com/package/openclaw/v/2026.5.20-beta.1",
      "- npm Telegram beta E2E: not supplied",
      "",
      "### Assets",
      "",
      "- artifact",
      "",
    ].join("\n");

    const merged = mergeTelegramProofIntoReleaseBody(
      body,
      "- npm Telegram beta E2E: https://github.com/openclaw/openclaw/actions/runs/123",
    );

    expect(merged).toContain("actions/runs/123");
    expect(merged).not.toContain("not supplied");
    expect(merged).toContain("### Assets");
  });

  it("inserts Telegram proof before the next release notes subsection", () => {
    const body = [
      "## Changes",
      "",
      "### Release verification",
      "",
      "- npm package: https://www.npmjs.com/package/openclaw/v/2026.5.20-beta.1",
      "",
      "### Assets",
      "",
      "- artifact",
      "",
    ].join("\n");

    const merged = mergeTelegramProofIntoReleaseBody(
      body,
      "- npm Telegram beta E2E: https://github.com/openclaw/openclaw/actions/runs/123",
    );

    expect(merged.indexOf("actions/runs/123")).toBeLessThan(merged.indexOf("### Assets"));
  });

  it("bounds child command hangs", () => {
    expect(() =>
      run(process.execPath, ["-e", "setInterval(() => {}, 1000)"], {
        capture: true,
        timeoutMs: 50,
      }),
    ).toThrow(/timed out after 50ms/u);
  });

  it("uses a non-ignorable timeout signal for trapped children", () => {
    expect(() =>
      run(
        process.execPath,
        ["-e", "process.on('SIGTERM', () => {}); setInterval(() => {}, 1000)"],
        {
          capture: true,
          timeoutMs: 50,
        },
      ),
    ).toThrow(/timed out after 50ms/u);
  });

  it("stops polling Telegram workflow runs after the timeout budget", async () => {
    let now = 0;
    const sleeps: number[] = [];

    await expect(
      pollRun("openclaw/openclaw", "123", {
        now: () => now,
        pollIntervalMs: 400,
        readRun: () => ({
          conclusion: null,
          html_url: "https://github.com/openclaw/openclaw/actions/runs/123",
          status: "queued",
          updated_at: "2026-05-28T12:00:00Z",
        }),
        sleep: async (ms) => {
          sleeps.push(ms);
          now += ms;
        },
        timeoutMs: 1000,
      }),
    ).rejects.toThrow("Telegram workflow 123 did not complete within 1000ms");
    expect(sleeps).toEqual([400, 400, 200]);
  });

  it("returns when the Telegram workflow succeeds", async () => {
    await expect(
      pollRun("openclaw/openclaw", "123", {
        readRun: () => ({
          conclusion: "success",
          html_url: "https://github.com/openclaw/openclaw/actions/runs/123",
          status: "completed",
          updated_at: "2026-05-28T12:00:00Z",
        }),
        sleep: async () => {
          throw new Error("sleep should not run after completion");
        },
      }),
    ).resolves.toBeUndefined();
  });
});
