Fix Discord double-ping when autodraft fires after manual pick #47
4 changed files with 58 additions and 39 deletions
|
|
@ -853,6 +853,7 @@ export async function executeAutoPick(params: {
|
||||||
participantName: participantToPick.name,
|
participantName: participantToPick.name,
|
||||||
sportName: participantToPick.sportsSeason.sport.name,
|
sportName: participantToPick.sportsSeason.sport.name,
|
||||||
pickNumber,
|
pickNumber,
|
||||||
|
nextPickNumber,
|
||||||
round: currentRound,
|
round: currentRound,
|
||||||
rawPickInRound,
|
rawPickInRound,
|
||||||
isDraftComplete,
|
isDraftComplete,
|
||||||
|
|
|
||||||
|
|
@ -338,6 +338,7 @@ export async function action(args: ActionFunctionArgs) {
|
||||||
participantName: participant.name,
|
participantName: participant.name,
|
||||||
sportName: participant.sportsSeason.sport.name,
|
sportName: participant.sportsSeason.sport.name,
|
||||||
pickNumber: currentPickNumber,
|
pickNumber: currentPickNumber,
|
||||||
|
nextPickNumber,
|
||||||
round: currentRound,
|
round: currentRound,
|
||||||
rawPickInRound,
|
rawPickInRound,
|
||||||
isDraftComplete,
|
isDraftComplete,
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ const BASE_PARAMS = {
|
||||||
participantName: "Erling Haaland",
|
participantName: "Erling Haaland",
|
||||||
sportName: "Soccer",
|
sportName: "Soccer",
|
||||||
pickNumber: 1,
|
pickNumber: 1,
|
||||||
|
nextPickNumber: 2,
|
||||||
round: 1,
|
round: 1,
|
||||||
rawPickInRound: 1,
|
rawPickInRound: 1,
|
||||||
isDraftComplete: false,
|
isDraftComplete: false,
|
||||||
|
|
@ -56,10 +57,6 @@ function makeLeague(overrides: object = {}) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeSeason(currentPickNumber = 2) {
|
|
||||||
return { id: SEASON_ID, leagueId: LEAGUE_ID, currentPickNumber };
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeTeam(id: string, ownerId: string | null = null, name = "Beta United") {
|
function makeTeam(id: string, ownerId: string | null = null, name = "Beta United") {
|
||||||
return { id, name, ownerId };
|
return { id, name, ownerId };
|
||||||
}
|
}
|
||||||
|
|
@ -70,19 +67,16 @@ function makeOwner(id: string, discordPingEnabled = true) {
|
||||||
|
|
||||||
function makeMockDb(overrides: {
|
function makeMockDb(overrides: {
|
||||||
league?: object | null;
|
league?: object | null;
|
||||||
season?: object | null;
|
|
||||||
nextTeam?: object | null;
|
nextTeam?: object | null;
|
||||||
owner?: object | null;
|
owner?: object | null;
|
||||||
} = {}) {
|
} = {}) {
|
||||||
const league = "league" in overrides ? overrides.league : makeLeague();
|
const league = "league" in overrides ? overrides.league : makeLeague();
|
||||||
const season = "season" in overrides ? overrides.season : makeSeason();
|
|
||||||
const nextTeam = "nextTeam" in overrides ? overrides.nextTeam : makeTeam(NEXT_TEAM_ID, NEXT_OWNER_ID);
|
const nextTeam = "nextTeam" in overrides ? overrides.nextTeam : makeTeam(NEXT_TEAM_ID, NEXT_OWNER_ID);
|
||||||
const owner = "owner" in overrides ? overrides.owner : makeOwner(NEXT_OWNER_ID);
|
const owner = "owner" in overrides ? overrides.owner : makeOwner(NEXT_OWNER_ID);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
query: {
|
query: {
|
||||||
leagues: { findFirst: vi.fn().mockResolvedValue(league) },
|
leagues: { findFirst: vi.fn().mockResolvedValue(league) },
|
||||||
seasons: { findFirst: vi.fn().mockResolvedValue(season) },
|
|
||||||
teams: { findFirst: vi.fn().mockResolvedValue(nextTeam) },
|
teams: { findFirst: vi.fn().mockResolvedValue(nextTeam) },
|
||||||
users: { findFirst: vi.fn().mockResolvedValue(owner) },
|
users: { findFirst: vi.fn().mockResolvedValue(owner) },
|
||||||
},
|
},
|
||||||
|
|
@ -159,24 +153,52 @@ describe("notifyPickMadeOnDiscord", () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passes nextTeamName when fresh season resolves the next slot", async () => {
|
it("passes nextTeamName based on caller-supplied nextPickNumber", async () => {
|
||||||
const db = makeMockDb({ season: makeSeason(2) }); // currentPickNumber=2 → pickInRound=2 → NEXT_TEAM_ID
|
// pick 3 in a 2-team snake: round 2 (reversed) → draftOrder 2 → NEXT_TEAM_ID → "Beta United"
|
||||||
|
const db = makeMockDb();
|
||||||
|
|
||||||
await notifyPickMadeOnDiscord({ ...BASE_PARAMS, db: db as never });
|
await notifyPickMadeOnDiscord({ ...BASE_PARAMS, nextPickNumber: 3, db: db as never });
|
||||||
|
|
||||||
expect(sendPickAnnouncementNotification).toHaveBeenCalledWith(
|
expect(sendPickAnnouncementNotification).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({ nextTeamName: "Beta United" })
|
expect.objectContaining({ nextTeamName: "Beta United" })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("reads next team from fresh DB pick number, not caller-supplied snapshot", async () => {
|
it("uses caller-supplied nextPickNumber instead of reading from DB", async () => {
|
||||||
// Simulate autodraft chain advancing 2 picks: currentPickNumber=3 on a 2-team snake
|
const db = makeMockDb();
|
||||||
// draft means pickInRound=2 again (round 2 reversed) → NEXT_TEAM_ID
|
|
||||||
const db = makeMockDb({ season: makeSeason(3) });
|
|
||||||
|
|
||||||
await notifyPickMadeOnDiscord({ ...BASE_PARAMS, db: db as never });
|
await notifyPickMadeOnDiscord({ ...BASE_PARAMS, db: db as never });
|
||||||
|
|
||||||
expect(db.query.seasons.findFirst).toHaveBeenCalledTimes(1);
|
expect((db.query as Record<string, unknown>).seasons).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows the immediate next drafter, not the post-autodraft-chain drafter", async () => {
|
||||||
|
// Regression: manual pick #1 by alpha; beta immediately autodrafts (#2); DB advances to pick #3.
|
||||||
|
// Discord for pick #1 must show beta ("On the clock: beta"), not gamma.
|
||||||
|
// Uses a 3-team snake: pick 1→alpha, pick 2→beta, pick 3→gamma.
|
||||||
|
const ALPHA_ID = "team-alpha";
|
||||||
|
const BETA_ID = "team-beta";
|
||||||
|
const GAMMA_ID = "team-gamma";
|
||||||
|
const threeTeamSlots = [
|
||||||
|
{ teamId: ALPHA_ID, draftOrder: 1 },
|
||||||
|
{ teamId: BETA_ID, draftOrder: 2 },
|
||||||
|
{ teamId: GAMMA_ID, draftOrder: 3 },
|
||||||
|
];
|
||||||
|
const betaTeam = { id: BETA_ID, name: "Beta Squad", ownerId: null };
|
||||||
|
const db = makeMockDb({ nextTeam: betaTeam });
|
||||||
|
|
||||||
|
await notifyPickMadeOnDiscord({
|
||||||
|
...BASE_PARAMS,
|
||||||
|
pickNumber: 1,
|
||||||
|
nextPickNumber: 2, // immediately after alpha's pick — before beta's autodraft
|
||||||
|
totalTeams: 3,
|
||||||
|
draftSlots: threeTeamSlots,
|
||||||
|
db: db as never,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sendPickAnnouncementNotification).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ nextTeamName: "Beta Squad" })
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passes nextOwnerDiscordId when owner has discordPingEnabled", async () => {
|
it("passes nextOwnerDiscordId when owner has discordPingEnabled", async () => {
|
||||||
|
|
@ -216,7 +238,7 @@ describe("notifyPickMadeOnDiscord", () => {
|
||||||
|
|
||||||
await notifyPickMadeOnDiscord({ ...BASE_PARAMS, isDraftComplete: true, db: db as never });
|
await notifyPickMadeOnDiscord({ ...BASE_PARAMS, isDraftComplete: true, db: db as never });
|
||||||
|
|
||||||
expect(db.query.seasons.findFirst).not.toHaveBeenCalled();
|
expect(db.query.teams.findFirst).not.toHaveBeenCalled();
|
||||||
expect(sendPickAnnouncementNotification).toHaveBeenCalledWith(
|
expect(sendPickAnnouncementNotification).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({ isDraftComplete: true, nextTeamName: undefined })
|
expect.objectContaining({ isDraftComplete: true, nextTeamName: undefined })
|
||||||
);
|
);
|
||||||
|
|
@ -235,7 +257,7 @@ describe("notifyPickMadeOnDiscord", () => {
|
||||||
it("uses sequential (not snake-adjusted) pick number in Discord title for even rounds", async () => {
|
it("uses sequential (not snake-adjusted) pick number in Discord title for even rounds", async () => {
|
||||||
// 13-team draft, pick #22: round 2 (even/reversed), rawPickInRound=9, snake-adjusted slot=5
|
// 13-team draft, pick #22: round 2 (even/reversed), rawPickInRound=9, snake-adjusted slot=5
|
||||||
// Discord should say "Round 2, Pick 9" not "Round 2, Pick 5"
|
// Discord should say "Round 2, Pick 9" not "Round 2, Pick 5"
|
||||||
const db = makeMockDb({ season: makeSeason(23) });
|
const db = makeMockDb();
|
||||||
|
|
||||||
await notifyPickMadeOnDiscord({
|
await notifyPickMadeOnDiscord({
|
||||||
...BASE_PARAMS,
|
...BASE_PARAMS,
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ export async function notifyPickMadeOnDiscord(params: {
|
||||||
participantName: string;
|
participantName: string;
|
||||||
sportName: string;
|
sportName: string;
|
||||||
pickNumber: number;
|
pickNumber: number;
|
||||||
|
nextPickNumber: number;
|
||||||
round: number;
|
round: number;
|
||||||
rawPickInRound: number;
|
rawPickInRound: number;
|
||||||
isDraftComplete: boolean;
|
isDraftComplete: boolean;
|
||||||
|
|
@ -37,6 +38,7 @@ export async function notifyPickMadeOnDiscord(params: {
|
||||||
participantName,
|
participantName,
|
||||||
sportName,
|
sportName,
|
||||||
pickNumber,
|
pickNumber,
|
||||||
|
nextPickNumber,
|
||||||
round,
|
round,
|
||||||
rawPickInRound,
|
rawPickInRound,
|
||||||
isDraftComplete,
|
isDraftComplete,
|
||||||
|
|
@ -57,28 +59,21 @@ export async function notifyPickMadeOnDiscord(params: {
|
||||||
let nextOwnerDiscordId: string | undefined;
|
let nextOwnerDiscordId: string | undefined;
|
||||||
|
|
||||||
if (!isDraftComplete) {
|
if (!isDraftComplete) {
|
||||||
// Read currentPickNumber fresh from DB so we see the post-autodraft-chain state,
|
const nextPickInRound = pickInRoundFor(nextPickNumber, totalTeams);
|
||||||
// matching the same guarantee sendOnTheClockEmail provides.
|
const nextSlot = draftSlots.find((s) => s.draftOrder === nextPickInRound);
|
||||||
const freshSeason = await db.query.seasons.findFirst({
|
if (nextSlot) {
|
||||||
where: eq(schema.seasons.id, seasonId),
|
const nextTeam = await db.query.teams.findFirst({
|
||||||
});
|
where: eq(schema.teams.id, nextSlot.teamId),
|
||||||
if (freshSeason) {
|
});
|
||||||
const nextPickInRound = pickInRoundFor(freshSeason.currentPickNumber ?? 1, totalTeams);
|
if (nextTeam) {
|
||||||
const nextSlot = draftSlots.find((s) => s.draftOrder === nextPickInRound);
|
nextTeamName = nextTeam.name;
|
||||||
if (nextSlot) {
|
if (nextTeam.ownerId) {
|
||||||
const nextTeam = await db.query.teams.findFirst({
|
const owner = await db.query.users.findFirst({
|
||||||
where: eq(schema.teams.id, nextSlot.teamId),
|
where: eq(schema.users.id, nextTeam.ownerId),
|
||||||
});
|
});
|
||||||
if (nextTeam) {
|
if (owner?.discordPingEnabled) {
|
||||||
nextTeamName = nextTeam.name;
|
const discordIds = await findDiscordIdsByUserIds([owner.id]);
|
||||||
if (nextTeam.ownerId) {
|
nextOwnerDiscordId = discordIds.get(owner.id);
|
||||||
const owner = await db.query.users.findFirst({
|
|
||||||
where: eq(schema.users.id, nextTeam.ownerId),
|
|
||||||
});
|
|
||||||
if (owner?.discordPingEnabled) {
|
|
||||||
const discordIds = await findDiscordIdsByUserIds([owner.id]);
|
|
||||||
nextOwnerDiscordId = discordIds.get(owner.id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue