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 = {}) { const mockDb: Record = { 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); } 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"); }); }); });