// packages/core-shared/src/instrumentation/sentry/sentry-tracer.test.ts
import { describe, it, expect, vi, beforeEach } from "vitest";

vi.mock("@sentry/nextjs", () => ({
  startSpan: vi.fn((_opts: unknown, fn: (span: unknown) => unknown) =>
    fn({ setAttribute: vi.fn(), setStatus: vi.fn() }),
  ),
}));

import * as Sentry from "@sentry/nextjs";
import { SentryTracer } from "@/instrumentation/sentry/sentry-tracer";

describe("SentryTracer", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("delegates startSpan to @sentry/nextjs.startSpan", async () => {
    const tracer = new SentryTracer();
    const result = await tracer.startSpan(
      { name: "blog.getArticles", op: "use-case" },
      async () => "value",
    );
    expect(result).toBe("value");
    expect(Sentry.startSpan).toHaveBeenCalledTimes(1);
    expect((Sentry.startSpan as ReturnType<typeof vi.fn>).mock.calls[0]![0]).toMatchObject({
      name: "blog.getArticles",
      op: "use-case",
    });
  });

  it("forwards attributes to Sentry", async () => {
    const tracer = new SentryTracer();
    await tracer.startSpan(
      { name: "articles.findAll", op: "repository", attributes: { collection: "articles", limit: 10 } },
      async () => undefined,
    );
    expect(
      (Sentry.startSpan as ReturnType<typeof vi.fn>).mock.calls[0]![0].attributes,
    ).toEqual({
      collection: "articles",
      limit: 10,
    });
  });

  it("propagates errors from the wrapped function", async () => {
    const tracer = new SentryTracer();
    await expect(
      tracer.startSpan({ name: "x" }, async () => {
        throw new Error("boom");
      }),
    ).rejects.toThrow("boom");
  });

  it("ISpan adapter forwards setAttribute and setStatus to Sentry's span", async () => {
    const sentrySpan = { setAttribute: vi.fn(), setStatus: vi.fn() };
    (Sentry.startSpan as ReturnType<typeof vi.fn>).mockImplementationOnce(
      (_opts: unknown, fn: (span: unknown) => unknown) => fn(sentrySpan),
    );
    const tracer = new SentryTracer();
    await tracer.startSpan({ name: "x" }, async (span) => {
      span.setAttribute("k", "v");
      span.setStatus("error", "msg");
      return undefined;
    });
    expect(sentrySpan.setAttribute).toHaveBeenCalledWith("k", "v");
    expect(sentrySpan.setStatus).toHaveBeenCalled();
  });
});
