← back to Govarbitrage

src/agents/skeptic-rules.test.ts

83 lines

import { describe, expect, it } from "vitest";
import { findCloneGroups, isClosingStale, isLowConfidence, type ValuationTuple } from "./skeptic-rules";

const t = (
  listingId: string,
  newRetail: number | null,
  expectedNetProfit: number | null,
  roi: number | null,
): ValuationTuple => ({ listingId, title: listingId, newRetail, expectedNetProfit, roi });

describe("findCloneGroups", () => {
  it("flags >=3 listings sharing the exact (newRetail, net, roi) tuple", () => {
    const items = [
      t("a", 900, 189, 2.14),
      t("b", 900, 189, 2.14),
      t("c", 900, 189, 2.14),
      t("d", 450, 80, 0.6),
    ];
    const groups = findCloneGroups(items);
    expect(groups).toHaveLength(1);
    expect(groups[0].members.map((m) => m.listingId)).toEqual(["a", "b", "c"]);
    expect(groups[0].key).toBe("$900 retail / $189 net / 214% ROI");
  });

  it("does not flag pairs (below minGroup)", () => {
    const items = [t("a", 900, 189, 2.14), t("b", 900, 189, 2.14), t("c", 450, 80, 0.6)];
    expect(findCloneGroups(items)).toHaveLength(0);
  });

  it("treats a small dollar difference as distinct", () => {
    const items = [t("a", 900, 189, 2.14), t("b", 901, 189, 2.14), t("c", 900, 189, 2.14)];
    expect(findCloneGroups(items)).toHaveLength(0);
  });

  it("ignores listings with missing values", () => {
    const items = [t("a", 900, 189, 2.14), t("b", 900, 189, 2.14), t("c", null, 189, 2.14)];
    expect(findCloneGroups(items)).toHaveLength(0);
  });

  it("returns multiple groups, largest first", () => {
    const items = [
      t("a", 900, 189, 2.14),
      t("b", 900, 189, 2.14),
      t("c", 900, 189, 2.14),
      t("d", 600, 120, 1.5),
      t("e", 600, 120, 1.5),
      t("f", 600, 120, 1.5),
      t("g", 600, 120, 1.5),
    ];
    const groups = findCloneGroups(items);
    expect(groups).toHaveLength(2);
    expect(groups[0].members).toHaveLength(4);
    expect(groups[1].members).toHaveLength(3);
  });
});

describe("isLowConfidence", () => {
  it("handles the 0..100 scale (Research.confidenceScore)", () => {
    expect(isLowConfidence(35)).toBe(true);
    expect(isLowConfidence(62)).toBe(false);
  });
  it("handles the 0..1 scale (identificationConfidence)", () => {
    expect(isLowConfidence(0.3)).toBe(true);
    expect(isLowConfidence(0.55)).toBe(false);
  });
  it("treats missing confidence as low", () => {
    expect(isLowConfidence(null)).toBe(true);
    expect(isLowConfidence(undefined)).toBe(true);
  });
});

describe("isClosingStale", () => {
  const now = new Date("2026-08-06T12:00:00Z");
  it("flags ACTIVE listings whose closingAt is in the past", () => {
    expect(isClosingStale(new Date("2026-08-05T12:00:00Z"), "ACTIVE", now)).toBe(true);
  });
  it("does not flag open or ended listings", () => {
    expect(isClosingStale(new Date("2026-08-07T12:00:00Z"), "ACTIVE", now)).toBe(false);
    expect(isClosingStale(new Date("2026-08-05T12:00:00Z"), "ENDED", now)).toBe(false);
    expect(isClosingStale(null, "ACTIVE", now)).toBe(false);
  });
});