import { describe, expect, it } from "vitest";
import { Readable } from "node:stream";
import { wrapSsrStreamWithNonce } from "./nonce-stream-transform.js";

function streamToString(stream: NodeJS.ReadableStream): Promise<string> {
  return new Promise((resolve, reject) => {
    const chunks: Buffer[] = [];
    stream.on("data", (chunk: Buffer) => chunks.push(Buffer.from(chunk)));
    stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
    stream.on("error", reject);
  });
}

function createStream(chunks: string[]): NodeJS.ReadableStream {
  return new Readable({
    read() {
      for (const chunk of chunks) {
        this.push(chunk);
      }
      this.push(null);
    },
  });
}

const NONCE = "test-nonce-abc123";

describe("wrapSsrStreamWithNonce", () => {
  it("injects nonce on bare <script> tags", async () => {
    const input = createStream(["<html><head><script>console.log('hi')</script></head></html>"]);
    const output = wrapSsrStreamWithNonce(input, NONCE);
    const result = await streamToString(output);
    expect(result).toContain(`<script nonce="${NONCE}">`);
  });

  it("injects nonce on <script src> tags", async () => {
    const input = createStream([`<script src="/app.js"></script>`]);
    const output = wrapSsrStreamWithNonce(input, NONCE);
    const result = await streamToString(output);
    expect(result).toContain(`<script nonce="${NONCE}" src="/app.js">`);
  });

  it("injects nonce on <script type='module'> tags", async () => {
    const input = createStream([`<script type="module" src="/app.js"></script>`]);
    const output = wrapSsrStreamWithNonce(input, NONCE);
    const result = await streamToString(output);
    expect(result).toContain(`<script nonce="${NONCE}" type="module" src="/app.js">`);
  });

  it("does not double-inject on scripts that already have nonce", async () => {
    const input = createStream([`<script nonce="existing">code</script>`]);
    const output = wrapSsrStreamWithNonce(input, NONCE);
    const result = await streamToString(output);
    expect(result).toBe(`<script nonce="existing">code</script>`);
    expect(result).not.toContain(NONCE);
  });

  it("handles multiple script tags in one chunk", async () => {
    const input = createStream([
      `<script>a</script><script nonce="x">b</script><script src="/c.js"></script>`,
    ]);
    const output = wrapSsrStreamWithNonce(input, NONCE);
    const result = await streamToString(output);
    expect(result).toContain(`<script nonce="${NONCE}">a</script>`);
    expect(result).toContain(`<script nonce="x">b</script>`);
    expect(result).toContain(`<script nonce="${NONCE}" src="/c.js">`);
  });

  it("handles chunks split mid-tag", async () => {
    const input = createStream([
      `<html><scr`,
      `ipt src="/app.js"></script></html>`,
    ]);
    const output = wrapSsrStreamWithNonce(input, NONCE);
    const result = await streamToString(output);
    expect(result).toContain(`<script nonce="${NONCE}" src="/app.js">`);
  });
});
