import { createHmac } from "node:crypto";
import { afterEach, describe, expect, it, vi } from "vitest";
import { createBridgePackagePublishPayload, createBridgePublishSignature, notifyBridgePackagePublished, readBridgePackageInfo, } from "./bridge-package-publish-hooks.js";
const originalEnv = { ...process.env };
function createLoggerMock() {
    return {
        info: vi.fn(),
        warn: vi.fn(),
        error: vi.fn(),
    };
}
function createPublishEnv(overrides = {}) {
    return {
        ...originalEnv,
        BRIDGE_PACKAGE_ON_PUBLIC_HOOKS_URL: "",
        BRIDGE_PACKAGE_ON_PUBLIC_HOOKS_SECRET: "",
        GITHUB_REPOSITORY: "owner/repo",
        GITHUB_REF: "refs/heads/main",
        GITHUB_SHA: "abc123",
        GITHUB_RUN_ID: "456",
        GITHUB_RUN_NUMBER: "789",
        ...overrides,
    };
}
const packageInfo = {
    name: "aws-runtime-bridge",
    version: "1.7.6",
};
afterEach(() => {
    process.env = { ...originalEnv };
    vi.clearAllMocks();
    vi.restoreAllMocks();
});
describe("bridge package publish hooks", () => {
    it("reads the bridge package name and version from package.json", async () => {
        await expect(readBridgePackageInfo()).resolves.toMatchObject({
            name: "aws-runtime-bridge",
            version: expect.stringMatching(/^\d+\.\d+\.\d+/),
        });
    });
    it("creates a publish payload with GitHub Actions metadata", () => {
        const payload = createBridgePackagePublishPayload(packageInfo, createPublishEnv(), () => new Date("2026-05-23T12:00:00.000Z"));
        expect(payload).toEqual({
            package: "aws-runtime-bridge",
            version: "1.7.6",
            repository: "owner/repo",
            ref: "refs/heads/main",
            sha: "abc123",
            runId: "456",
            runNumber: "789",
            publishedAt: "2026-05-23T12:00:00.000Z",
        });
    });
    it("signs the exact request body with HMAC SHA-256", () => {
        const body = JSON.stringify({ package: "aws-runtime-bridge", version: "1.7.6" });
        const secret = "test-secret";
        const expectedDigest = createHmac("sha256", secret).update(body, "utf-8").digest("hex");
        expect(createBridgePublishSignature(body, secret)).toBe(`sha256=${expectedDigest}`);
    });
    it("skips notification when both hook URL and secret are absent", async () => {
        const post = vi.fn();
        const logger = createLoggerMock();
        await expect(notifyBridgePackagePublished({
            env: createPublishEnv(),
            logger,
            packageInfo,
            post,
        })).resolves.toBe(false);
        expect(post).not.toHaveBeenCalled();
        expect(logger.info).toHaveBeenCalledWith("Bridge package publish hook is not configured. Skipping notification.");
        expect(JSON.stringify(logger)).not.toContain("test-secret");
    });
    it("skips notification when only one hook setting is configured", async () => {
        const post = vi.fn();
        const logger = createLoggerMock();
        await expect(notifyBridgePackagePublished({
            env: createPublishEnv({
                BRIDGE_PACKAGE_ON_PUBLIC_HOOKS_URL: "https://hooks.example.test/bridge",
                BRIDGE_PACKAGE_ON_PUBLIC_HOOKS_SECRET: "",
            }),
            logger,
            packageInfo,
            post,
        })).resolves.toBe(false);
        expect(post).not.toHaveBeenCalled();
        expect(logger.warn).toHaveBeenCalledWith("Bridge package publish hook requires both BRIDGE_PACKAGE_ON_PUBLIC_HOOKS_URL and BRIDGE_PACKAGE_ON_PUBLIC_HOOKS_SECRET. Skipping notification.");
    });
    it("sends signed publish notification when hook URL and secret are configured", async () => {
        const post = vi.fn().mockResolvedValue({ data: { success: true } });
        const logger = createLoggerMock();
        const secret = "super-secret-value";
        const env = createPublishEnv({
            BRIDGE_PACKAGE_ON_PUBLIC_HOOKS_URL: "https://hooks.example.test/bridge?token=url-token",
            BRIDGE_PACKAGE_ON_PUBLIC_HOOKS_SECRET: secret,
        });
        await expect(notifyBridgePackagePublished({
            env,
            logger,
            packageInfo,
            now: () => new Date("2026-05-23T12:00:00.000Z"),
            post,
        })).resolves.toBe(true);
        expect(post).toHaveBeenCalledTimes(1);
        const [url, body, config] = post.mock.calls[0] ?? [];
        expect(url).toBe("https://hooks.example.test/bridge?token=url-token");
        expect(typeof body).toBe("string");
        expect(JSON.parse(body)).toMatchObject({
            package: "aws-runtime-bridge",
            version: "1.7.6",
            publishedAt: "2026-05-23T12:00:00.000Z",
        });
        expect(config).toMatchObject({
            headers: {
                "Content-Type": "application/json",
                "X-Bridge-Signature": createBridgePublishSignature(body, secret),
                "X-Bridge-Timestamp": "1779537600",
            },
            timeout: 10_000,
        });
        expect(logger.info).toHaveBeenCalledWith("Notifying bridge package publish hook for aws-runtime-bridge@1.7.6 (https://hooks.example.test).");
        expect(JSON.stringify(logger.info.mock.calls)).not.toContain(secret);
        expect(JSON.stringify(logger.info.mock.calls)).not.toContain("url-token");
    });
    it("surfaces notification failures when hook is configured", async () => {
        const post = vi.fn().mockRejectedValue(new Error("network unavailable"));
        await expect(notifyBridgePackagePublished({
            env: createPublishEnv({
                BRIDGE_PACKAGE_ON_PUBLIC_HOOKS_URL: "https://hooks.example.test/bridge",
                BRIDGE_PACKAGE_ON_PUBLIC_HOOKS_SECRET: "test-secret",
            }),
            logger: createLoggerMock(),
            packageInfo,
            post,
        })).rejects.toThrow("network unavailable");
    });
});
