brackt/app/models/__tests__/auto-pick.test.ts
Chris Parsons b83d1c8643 fix: remove erroneous ?? fallbacks in autodraft socket emissions and add autoPickForTeam tests
- Remove `?? true` default on queueOnly in queue-empty auto-disable socket emit
  (line 488) — was dead code since the column is NOT NULL, but semantically wrong
  and would have caused client-side UI desync if the type ever relaxed
- Remove `?? false` default on the next_pick auto-disable path for consistency
- Add app/models/__tests__/auto-pick.test.ts with 6 tests covering the queueOnly
  constraint: empty queue, all items drafted, partial queue skip, and EV fallback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 21:44:43 -08:00

304 lines
9.2 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 { describe, it, expect, vi, beforeEach } from "vitest";
import { autoPickForTeam } from "../draft-utils";
// Mock all external model dependencies so we can control their return values
vi.mock("~/models/draft-pick", () => ({
getDraftPicksWithSports: vi.fn(),
getTeamDraftPicksWithSports: vi.fn(),
isParticipantDrafted: vi.fn(),
}));
vi.mock("~/models/participant", () => ({
getParticipantsForSeasonWithSports: vi.fn(),
}));
vi.mock("~/models/season-sport", () => ({
getSeasonSportsSimple: vi.fn(),
}));
vi.mock("~/models/draft-queue", () => ({
getTeamQueue: vi.fn(),
}));
vi.mock("~/lib/draft-eligibility", () => ({
calculateDraftEligibility: vi.fn(),
}));
// Prevent actual socket/db usage
vi.mock("~/server/socket");
vi.mock("~/database/context");
import {
getDraftPicksWithSports,
getTeamDraftPicksWithSports,
isParticipantDrafted,
} from "~/models/draft-pick";
import { getParticipantsForSeasonWithSports } from "~/models/participant";
import { getSeasonSportsSimple } from "~/models/season-sport";
import { getTeamQueue } from "~/models/draft-queue";
import { calculateDraftEligibility } from "~/lib/draft-eligibility";
const SEASON_ID = "season-1";
const TEAM_ID = "team-1";
const DRAFT_ROUNDS = 10;
const ALL_TEAM_IDS = [TEAM_ID, "team-2"];
// A minimal mock db for cases that do NOT reach getTopAvailableParticipant.
// `where` resolves to [] so `await db.select()...where()` yields an array.
function makeMockDb(overrides: Record<string, unknown> = {}) {
const mockDb: Record<string, unknown> = {
query: {
participants: {
findMany: vi.fn().mockResolvedValue([]),
},
},
select: vi.fn().mockReturnThis(),
from: vi.fn().mockReturnThis(),
innerJoin: vi.fn().mockReturnThis(),
where: vi.fn().mockResolvedValue([]),
orderBy: vi.fn().mockResolvedValue([]),
delete: vi.fn().mockReturnThis(),
...overrides,
};
return mockDb;
}
// Build a mock db whose select chain returns a real EV participant.
// getTopAvailableParticipant makes three sequential awaitable calls:
// 1. select().from().where() → drafted picks → []
// 2. select().from().innerJoin()×2.where() → season sports → [{sportsSeasonId, sportId}]
// 3. select().from().where().orderBy() → top participant → [{id, ...}]
function makeMockDbWithEvParticipant(participantId: string) {
return {
query: { participants: { findMany: vi.fn().mockResolvedValue([]) } },
select: vi.fn().mockReturnThis(),
from: vi.fn().mockReturnThis(),
innerJoin: vi.fn().mockReturnThis(),
where: vi.fn()
.mockResolvedValueOnce([]) // call 1: drafted picks
.mockResolvedValueOnce([{ sportsSeasonId: "ss-1", sportId: "sport-1" }]) // call 2: season sports
.mockReturnThis(), // call 3: chain to orderBy
orderBy: vi.fn().mockResolvedValue([
{ id: participantId, name: "EV Player", expectedValue: "100.00" },
]),
delete: vi.fn().mockReturnThis(),
};
}
// Minimal eligibility stub — makes every sport eligible.
function stubEligibility() {
vi.mocked(calculateDraftEligibility).mockReturnValue({
eligibleSportIds: new Set(["sport-1"]),
} as ReturnType<typeof calculateDraftEligibility>);
}
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(getDraftPicksWithSports).mockResolvedValue([]);
vi.mocked(getTeamDraftPicksWithSports).mockResolvedValue([]);
vi.mocked(getParticipantsForSeasonWithSports).mockResolvedValue([]);
vi.mocked(getSeasonSportsSimple).mockResolvedValue([]);
stubEligibility();
});
describe("autoPickForTeam queueOnly constraint (AC2 & AC3)", () => {
describe("empty queue", () => {
it("returns null when queueOnly=true (no EV fallback)", async () => {
vi.mocked(getTeamQueue).mockResolvedValue([]);
const result = await autoPickForTeam(
SEASON_ID,
TEAM_ID,
DRAFT_ROUNDS,
ALL_TEAM_IDS,
makeMockDb() as any,
true // queueOnly
);
expect(result).toBeNull();
});
it("returns the top EV participant when queueOnly=false (falls back to EV)", async () => {
vi.mocked(getTeamQueue).mockResolvedValue([]);
const mockDb = makeMockDbWithEvParticipant("p-ev");
const result = await autoPickForTeam(
SEASON_ID,
TEAM_ID,
DRAFT_ROUNDS,
ALL_TEAM_IDS,
mockDb as any,
false // queueOnly off
);
// Must return the EV participant — proves the EV path ran, not the queueOnly guard.
expect(result).toBe("p-ev");
});
});
describe("all queue items already drafted", () => {
it("returns null when queueOnly=true and every queued participant is taken", async () => {
const queue = [
{ id: "q-1", participantId: "p-1", queuePosition: 1 },
{ id: "q-2", participantId: "p-2", queuePosition: 2 },
];
vi.mocked(getTeamQueue).mockResolvedValue(queue as any);
const mockDb = makeMockDb({
query: {
participants: {
findMany: vi.fn().mockResolvedValue([
{
id: "p-1",
name: "Player One",
sportsSeason: { sport: { id: "sport-1", name: "NFL" } },
},
{
id: "p-2",
name: "Player Two",
sportsSeason: { sport: { id: "sport-1", name: "NFL" } },
},
]),
},
},
});
// Both participants are already drafted
vi.mocked(isParticipantDrafted).mockResolvedValue(true);
const result = await autoPickForTeam(
SEASON_ID,
TEAM_ID,
DRAFT_ROUNDS,
ALL_TEAM_IDS,
mockDb as any,
true // queueOnly
);
expect(result).toBeNull();
});
it("removes stale queue entries when all items are drafted", async () => {
const queue = [{ id: "q-1", participantId: "p-1", queuePosition: 1 }];
vi.mocked(getTeamQueue).mockResolvedValue(queue as any);
const mockDelete = vi.fn().mockReturnThis();
const mockWhere = vi.fn().mockResolvedValue(undefined);
const mockDb = makeMockDb({
query: {
participants: {
findMany: vi.fn().mockResolvedValue([
{
id: "p-1",
name: "Player One",
sportsSeason: { sport: { id: "sport-1", name: "NFL" } },
},
]),
},
},
delete: mockDelete,
where: mockWhere,
});
vi.mocked(isParticipantDrafted).mockResolvedValue(true);
await autoPickForTeam(
SEASON_ID,
TEAM_ID,
DRAFT_ROUNDS,
ALL_TEAM_IDS,
mockDb as any,
true
);
// Stale items should be cleaned from the queue
expect(mockDelete).toHaveBeenCalled();
});
});
describe("partial queue — first item drafted, second available", () => {
it("skips drafted item and returns the next valid participant", async () => {
const queue = [
{ id: "q-1", participantId: "p-1", queuePosition: 1 },
{ id: "q-2", participantId: "p-2", queuePosition: 2 },
];
vi.mocked(getTeamQueue).mockResolvedValue(queue as any);
const mockDb = makeMockDb({
query: {
participants: {
findMany: vi.fn().mockResolvedValue([
{
id: "p-1",
name: "Player One",
sportsSeason: { sport: { id: "sport-1", name: "NFL" } },
},
{
id: "p-2",
name: "Player Two",
sportsSeason: { sport: { id: "sport-1", name: "NFL" } },
},
]),
},
},
});
// p-1 is already drafted, p-2 is available
vi.mocked(isParticipantDrafted).mockImplementation(
async (_seasonId, participantId) => participantId === "p-1"
);
const result = await autoPickForTeam(
SEASON_ID,
TEAM_ID,
DRAFT_ROUNDS,
ALL_TEAM_IDS,
mockDb as any,
true // queueOnly — still uses queue, just no EV fallback
);
expect(result).toBe("p-2");
});
it("returns the first available participant when none are drafted (happy path)", async () => {
const queue = [
{ id: "q-1", participantId: "p-1", queuePosition: 1 },
{ id: "q-2", participantId: "p-2", queuePosition: 2 },
];
vi.mocked(getTeamQueue).mockResolvedValue(queue as any);
const mockDb = makeMockDb({
query: {
participants: {
findMany: vi.fn().mockResolvedValue([
{
id: "p-1",
name: "Player One",
sportsSeason: { sport: { id: "sport-1", name: "NFL" } },
},
{
id: "p-2",
name: "Player Two",
sportsSeason: { sport: { id: "sport-1", name: "NFL" } },
},
]),
},
},
});
vi.mocked(isParticipantDrafted).mockResolvedValue(false);
const result = await autoPickForTeam(
SEASON_ID,
TEAM_ID,
DRAFT_ROUNDS,
ALL_TEAM_IDS,
mockDb as any,
true
);
expect(result).toBe("p-1");
});
});
});