/**
 * GraphQL must not disclose other users' email addresses.
 *
 * `user(username: "...")` selected users.email and returned it verbatim. The
 * resolver is reachable anonymously (POST /api/graphql, softAuth only), so an
 * unauthenticated caller could read the address of any account on the
 * platform — bulk harvesting with one cheap query per username.
 *
 * Verified live 2026-07-28 before the fix:
 *   $ curl -s -X POST https://gluecron.com/api/graphql \
 *       -d '{"query":"{ user(username:\"ccantynz\"){ username email } }"}'
 *   {"data":{"user":{"username":"ccantynz","email":"ccantynz@gmail.com"}}}
 *
 * The `me` resolver remains the supported way to read your own address; it is
 * already scoped to ctx.user.id.
 */

import { describe, expect, it } from "bun:test";
import { readFileSync } from "fs";

const SRC = readFileSync("src/lib/graphql.ts", "utf8");
const userResolver = SRC.slice(
  SRC.indexOf("user: async (args, sel, _ctx)"),
  SRC.indexOf("repository: async (args, sel, _ctx)")
);

describe("user(username:) email exposure", () => {
  it("only returns email to the account holder", () => {
    expect(userResolver).toContain("_ctx?.user?.id === row.id");
    expect(userResolver).toContain("email: isSelf ? row.email : null");
  });

  it("does not pass the raw row through to selection resolution", () => {
    // The bug shape: `resolveSelections(u, sel)` where u came straight from
    // the DB with email attached.
    const idx = userResolver.indexOf("const u = {");
    expect(idx).toBeGreaterThan(-1);
    // The redacted object must be built before anything consumes it.
    const consume = userResolver.indexOf("resolveSelections(u");
    if (consume > -1) expect(idx).toBeLessThan(consume);
  });

  it("nulls the field rather than dropping it", () => {
    // Clients that request `email` should still get a valid response shape.
    expect(userResolver).toContain(": null");
  });
});

describe("the viewer resolver is unaffected", () => {
  it("still scopes to the authenticated user", () => {
    // `viewer`, not `me` — it is the self-lookup and is already gated on
    // ctx.user, so reading your own address keeps working.
    const viewer = SRC.slice(
      SRC.indexOf("viewer: async"),
      SRC.indexOf("user: async")
    );
    expect(viewer).toContain("ctx.user.id");
    expect(viewer).toContain("email: users.email");
    expect(viewer).toContain("if (!ctx.user) return null");
  });
});

describe("no other resolver selects email", () => {
  it("only viewer and user touch users.email", () => {
    const hits = SRC.match(/email: users\.email/g) ?? [];
    expect(hits.length).toBe(2); // viewer (scoped) + user (now redacted)
  });
});
