brackt/app/routes/__tests__/admin.simulators.test.ts
Claude 5f70852998
Address review: free-typing blend inputs, honest blend label, fix stale comment
- Simulator setup blend fields now keep a draft text buffer per field so an
  admin can clear/retype a weight without it snapping to 0/100; the committed
  futuresPct only re-syncs on a parseable number and normalizes on blur.
- futuresBlendLabel clamps a genuine blend to 1–99% so a near-extreme weight
  (e.g. 0.999) never reads as "0% Elo / 100% Futures"; the 0/1 extremes stay
  reserved for the "Elo only" / "overrides Elo" labels. Added test coverage.
- Refresh the stale comment in the simulate stub route, which referenced the
  removed intent="simulate".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RvJnQPEJNRxGipSad7q2T
2026-06-26 23:17:54 +00:00

174 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("~/models/simulator", () => ({
listSportsSeasonSimulatorSummaries: vi.fn(),
}));
vi.mock("~/services/simulations/runner", () => ({
runSportsSeasonSimulation: vi.fn(),
}));
import { action, futuresBlendLabel, loader } from "../admin.simulators";
import { listSportsSeasonSimulatorSummaries } from "~/models/simulator";
import { runSportsSeasonSimulation } from "~/services/simulations/runner";
function postForm(entries: Record<string, string | string[]>) {
const body = new URLSearchParams();
for (const [key, value] of Object.entries(entries)) {
if (Array.isArray(value)) {
for (const item of value) body.append(key, item);
} else {
body.set(key, value);
}
}
return new Request("http://test/admin/simulators", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: body.toString(),
});
}
beforeEach(() => {
vi.clearAllMocks();
});
describe("admin simulators loader", () => {
it("hides completed seasons", async () => {
vi.mocked(listSportsSeasonSimulatorSummaries).mockResolvedValue([
{
sportsSeasonId: "season-upcoming",
seasonName: "2026",
year: 2026,
seasonStatus: "upcoming",
simulationStatus: "idle",
fantasySeasonId: null,
fantasySeasonName: null,
leagueName: null,
sportName: "NBA",
sportSlug: "nba",
simulatorType: "nba_bracket",
simulatorName: "NBA Bracket",
participantCount: 16,
participantInputCount: 16,
lastSimulatedDate: null,
readiness: {
status: "ready",
canRun: true,
participantInputCount: 16,
participantCount: 16,
missingInputs: [],
warnings: [],
},
},
{
sportsSeasonId: "season-completed",
seasonName: "2025",
year: 2025,
seasonStatus: "completed",
simulationStatus: "idle",
fantasySeasonId: null,
fantasySeasonName: null,
leagueName: null,
sportName: "NFL",
sportSlug: "nfl",
simulatorType: "nfl_bracket",
simulatorName: "NFL Bracket",
participantCount: 14,
participantInputCount: 14,
lastSimulatedDate: "2026-05-11",
readiness: {
status: "ready",
canRun: true,
participantInputCount: 14,
participantCount: 14,
missingInputs: [],
warnings: [],
},
},
] as never);
const response = await loader();
expect(response).toEqual({
simulators: [
expect.objectContaining({
sportsSeasonId: "season-upcoming",
seasonStatus: "upcoming",
}),
],
sports: ["NBA"],
simulatorTypes: ["nba_bracket"],
});
});
});
describe("futuresBlendLabel", () => {
it("shows the full Elo/Futures split for an in-between weight", () => {
expect(futuresBlendLabel(0.3)).toBe("70% Elo / 30% Futures");
expect(futuresBlendLabel(0.5)).toBe("50% Elo / 50% Futures");
});
it("labels the extremes without percentages", () => {
expect(futuresBlendLabel(0)).toBe("Elo only");
expect(futuresBlendLabel(1)).toBe("overrides Elo");
});
it("keeps a near-extreme blend within 199% so it never reads as 0/100", () => {
expect(futuresBlendLabel(0.999)).toBe("1% Elo / 99% Futures");
expect(futuresBlendLabel(0.004)).toBe("99% Elo / 1% Futures");
});
});
describe("admin simulators action", () => {
it("runs one simulator", async () => {
vi.mocked(runSportsSeasonSimulation).mockResolvedValue({
sportsSeasonId: "season-1",
simulatorType: "nba_bracket",
simulatedParticipants: 30,
zeroedParticipants: 0,
snapshotDate: "2026-05-11",
});
const response = await action({
request: postForm({ intent: "run-one", sportsSeasonId: "season-1" }),
params: {},
context: {},
} as never);
expect(runSportsSeasonSimulation).toHaveBeenCalledWith("season-1");
expect(response).toEqual({
success: true,
message: "Simulation run complete: 1/1 succeeded.",
results: [{ sportsSeasonId: "season-1", ok: true, message: "Simulated 30 participant(s)." }],
});
});
it("runs selected simulators sequentially and reports failures", async () => {
vi.mocked(runSportsSeasonSimulation)
.mockResolvedValueOnce({
sportsSeasonId: "season-1",
simulatorType: "nba_bracket",
simulatedParticipants: 30,
zeroedParticipants: 0,
snapshotDate: "2026-05-11",
})
.mockRejectedValueOnce(new Error("Simulator is not ready: source odds."));
const response = await action({
request: postForm({ intent: "run-selected", sportsSeasonId: ["season-1", "season-2"] }),
params: {},
context: {},
} as never);
expect(runSportsSeasonSimulation).toHaveBeenNthCalledWith(1, "season-1");
expect(runSportsSeasonSimulation).toHaveBeenNthCalledWith(2, "season-2");
expect(response).toEqual({
success: false,
message: "Simulation run complete: 1/2 succeeded.",
results: [
{ sportsSeasonId: "season-1", ok: true, message: "Simulated 30 participant(s)." },
{ sportsSeasonId: "season-2", ok: false, message: "Simulator is not ready: source odds." },
],
});
});
});