syntaxai/tdd.md · main · src / c14_real_reports.test.ts

c14_real_reports.test.ts 102 lines · 3746 bytes raw
// Sibling test for c32_real_reports.ts. buildLiveReports itself fans out
// to fetchRepoCommits (network) so its end-to-end shape is covered by
// the live /reports/live route. The pure helpers underneath — agent
// attribution from commit messages, and the 30-day daily sparkline —
// are unit-testable here.

import { describe, test, expect } from "bun:test";
import {
  detectAgent,
  buildTrend,
  buildLiveReports,
} from "./c14_real_reports.ts";
import type { GithubCommit } from "./c14_github.ts";

const mkCommit = (date: string, message = ""): GithubCommit => ({
  sha: "0".repeat(40),
  commit: {
    message,
    author: { name: "test", email: "[email protected]", date },
    committer: { name: "test", email: "[email protected]", date },
  },
  author: null,
  committer: null,
} as unknown as GithubCommit);

describe("c32_real_reports — detectAgent", () => {
  test("recognises a Claude Code commit via Co-Authored-By: Claude", () => {
    expect(detectAgent("Add feature\n\nCo-Authored-By: Claude <noreply>")).toBe("claude-code");
  });

  test("recognises a Cursor commit", () => {
    expect(detectAgent("Fix bug\n\nCo-Authored-By: Cursor <[email protected]>")).toBe("cursor");
  });

  test("recognises an Aider commit", () => {
    expect(detectAgent("Refactor x\n\nCo-Authored-By: aider")).toBe("aider");
  });

  test("returns unknown when no recognised footer is present", () => {
    expect(detectAgent("Just a commit")).toBe("unknown");
    expect(detectAgent("")).toBe("unknown");
  });

  test("the regex is case-insensitive on the agent token", () => {
    expect(detectAgent("Co-Authored-By: CLAUDE")).toBe("claude-code");
    expect(detectAgent("co-authored-by: CURSOR")).toBe("cursor");
  });
});

describe("c32_real_reports — buildTrend (30-day daily sparkline)", () => {
  // Use today (UTC) as the anchor — the function compares against UTC
  // midnight, so we need ISO strings that fall on the right days.
  const today = new Date();
  today.setUTCHours(0, 0, 0, 0);
  const iso = (daysAgo: number): string => {
    const d = new Date(today.getTime() - daysAgo * 24 * 60 * 60 * 1000);
    return d.toISOString();
  };

  test("returns an array of `days` length", () => {
    expect(buildTrend([], 30)).toHaveLength(30);
    expect(buildTrend([], 7)).toHaveLength(7);
  });

  test("empty input flat-lines at zero", () => {
    const trend = buildTrend([], 7);
    expect(trend.every((n) => n === 0)).toBe(true);
  });

  test("a single commit today increments the last bucket", () => {
    const trend = buildTrend([mkCommit(iso(0))], 7);
    expect(trend[trend.length - 1]).toBe(1);
    expect(trend.slice(0, -1).every((n) => n === 0)).toBe(true);
  });

  test("multiple commits on the same day stack in the same bucket", () => {
    const trend = buildTrend([mkCommit(iso(0)), mkCommit(iso(0)), mkCommit(iso(0))], 7);
    expect(trend[trend.length - 1]).toBe(3);
  });

  test("commits older than the window are dropped", () => {
    const trend = buildTrend([mkCommit(iso(99))], 7);
    expect(trend.every((n) => n === 0)).toBe(true);
  });

  test("a commit `daysAgo` lands at index `days - 1 - daysAgo`", () => {
    const trend = buildTrend([mkCommit(iso(2))], 7);
    // index 6 = today, 5 = yesterday, 4 = 2 days ago
    expect(trend[4]).toBe(1);
  });
});

describe("c32_real_reports — orchestrator entry point", () => {
  test("buildLiveReports is exported as an async function", () => {
    expect(typeof buildLiveReports).toBe("function");
    // End-to-end coverage lives on /reports/live; this is the structural
    // smoke that the export shape didn't drift. `.length` counts only
    // non-default params (owner, repo) — perPage carries a default.
    expect(buildLiveReports.length).toBe(2);
  });
});