240 lines
7.8 KiB
TypeScript
240 lines
7.8 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
|
|
vi.mock("~/database/context", () => ({
|
|
database: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("~/services/discord", () => ({
|
|
sendPickAnnouncementNotification: vi.fn().mockResolvedValue(undefined),
|
|
}));
|
|
|
|
vi.mock("~/models/account", () => ({
|
|
findDiscordIdsByUserIds: vi.fn().mockResolvedValue(new Map()),
|
|
}));
|
|
|
|
import { notifyPickMadeOnDiscord } from "../draft-discord.server";
|
|
import { sendPickAnnouncementNotification } from "~/services/discord";
|
|
import { findDiscordIdsByUserIds } from "~/models/account";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Fixtures
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const SEASON_ID = "season-1";
|
|
const LEAGUE_ID = "league-1";
|
|
const NEXT_OWNER_ID = "owner-2";
|
|
const WEBHOOK_URL = "https://discord.com/api/webhooks/123/abc";
|
|
|
|
const BASE_PARAMS = {
|
|
seasonId: SEASON_ID,
|
|
leagueId: LEAGUE_ID,
|
|
pickedTeamName: "Alpha FC",
|
|
participantName: "Erling Haaland",
|
|
sportName: "Soccer",
|
|
pickNumber: 1,
|
|
round: 1,
|
|
pickInRound: 1,
|
|
isDraftComplete: false,
|
|
nextTeamName: "Beta United",
|
|
nextTeamOwnerId: NEXT_OWNER_ID,
|
|
};
|
|
|
|
function makeLeague(overrides: object = {}) {
|
|
return {
|
|
id: LEAGUE_ID,
|
|
discordWebhookUrl: WEBHOOK_URL,
|
|
discordPicksAnnouncementEnabled: true,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function makeOwner(id: string, discordPingEnabled = true) {
|
|
return { id, discordPingEnabled };
|
|
}
|
|
|
|
function makeMockDb(overrides: {
|
|
league?: object | null;
|
|
owner?: object | null;
|
|
} = {}) {
|
|
const league = "league" in overrides ? overrides.league : makeLeague();
|
|
const owner = "owner" in overrides ? overrides.owner : makeOwner(NEXT_OWNER_ID);
|
|
|
|
return {
|
|
query: {
|
|
leagues: { findFirst: vi.fn().mockResolvedValue(league) },
|
|
users: { findFirst: vi.fn().mockResolvedValue(owner) },
|
|
},
|
|
};
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.mocked(sendPickAnnouncementNotification).mockResolvedValue(undefined);
|
|
process.env.APP_URL = "https://test.brackt.com";
|
|
});
|
|
|
|
afterEach(() => {
|
|
delete process.env.APP_URL;
|
|
});
|
|
|
|
describe("notifyPickMadeOnDiscord", () => {
|
|
it("returns early when league has no webhook URL", async () => {
|
|
const db = makeMockDb({ league: makeLeague({ discordWebhookUrl: null }) });
|
|
|
|
await notifyPickMadeOnDiscord({ ...BASE_PARAMS, db: db as never });
|
|
|
|
expect(sendPickAnnouncementNotification).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("returns early when picks announcement toggle is disabled", async () => {
|
|
const db = makeMockDb({ league: makeLeague({ discordPicksAnnouncementEnabled: false }) });
|
|
|
|
await notifyPickMadeOnDiscord({ ...BASE_PARAMS, db: db as never });
|
|
|
|
expect(sendPickAnnouncementNotification).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("returns early when league is not found", async () => {
|
|
const db = makeMockDb({ league: null });
|
|
|
|
await notifyPickMadeOnDiscord({ ...BASE_PARAMS, db: db as never });
|
|
|
|
expect(sendPickAnnouncementNotification).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("sends announcement with correct pick details", async () => {
|
|
const db = makeMockDb();
|
|
|
|
await notifyPickMadeOnDiscord({ ...BASE_PARAMS, db: db as never });
|
|
|
|
expect(sendPickAnnouncementNotification).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
webhookUrl: WEBHOOK_URL,
|
|
pickedTeamName: "Alpha FC",
|
|
participantName: "Erling Haaland",
|
|
sportName: "Soccer",
|
|
pickNumber: 1,
|
|
round: 1,
|
|
pickInRound: 1,
|
|
isDraftComplete: false,
|
|
})
|
|
);
|
|
});
|
|
|
|
it("builds the draft URL from APP_URL env var", async () => {
|
|
const db = makeMockDb();
|
|
|
|
await notifyPickMadeOnDiscord({ ...BASE_PARAMS, db: db as never });
|
|
|
|
expect(sendPickAnnouncementNotification).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
draftUrl: `https://test.brackt.com/leagues/${LEAGUE_ID}/draft/${SEASON_ID}`,
|
|
})
|
|
);
|
|
});
|
|
|
|
it("forwards caller-supplied nextTeamName to the notification", async () => {
|
|
const db = makeMockDb();
|
|
|
|
await notifyPickMadeOnDiscord({ ...BASE_PARAMS, nextTeamName: "Beta United", db: db as never });
|
|
|
|
expect(sendPickAnnouncementNotification).toHaveBeenCalledWith(
|
|
expect.objectContaining({ nextTeamName: "Beta United" })
|
|
);
|
|
});
|
|
|
|
it("does not query the teams table (team resolution is the caller's responsibility)", async () => {
|
|
const db = makeMockDb();
|
|
|
|
await notifyPickMadeOnDiscord({ ...BASE_PARAMS, db: db as never });
|
|
|
|
expect((db.query as Record<string, unknown>).teams).toBeUndefined();
|
|
});
|
|
|
|
it("forwards caller-supplied nextTeamName regardless of pick number", async () => {
|
|
// Callers must pass the correct nextTeamName; this service just forwards it.
|
|
const db = makeMockDb();
|
|
|
|
await notifyPickMadeOnDiscord({ ...BASE_PARAMS, nextTeamName: "Beta Squad", db: db as never });
|
|
|
|
expect(sendPickAnnouncementNotification).toHaveBeenCalledWith(
|
|
expect.objectContaining({ nextTeamName: "Beta Squad" })
|
|
);
|
|
});
|
|
|
|
it("passes nextOwnerDiscordId when owner has discordPingEnabled", async () => {
|
|
vi.mocked(findDiscordIdsByUserIds).mockResolvedValue(new Map([[NEXT_OWNER_ID, "discord-999"]]));
|
|
const db = makeMockDb();
|
|
|
|
await notifyPickMadeOnDiscord({ ...BASE_PARAMS, db: db as never });
|
|
|
|
expect(sendPickAnnouncementNotification).toHaveBeenCalledWith(
|
|
expect.objectContaining({ nextOwnerDiscordId: "discord-999" })
|
|
);
|
|
});
|
|
|
|
it("omits nextOwnerDiscordId when owner has discordPingEnabled: false", async () => {
|
|
const db = makeMockDb({ owner: makeOwner(NEXT_OWNER_ID, false) });
|
|
|
|
await notifyPickMadeOnDiscord({ ...BASE_PARAMS, db: db as never });
|
|
|
|
expect(findDiscordIdsByUserIds).not.toHaveBeenCalled();
|
|
expect(sendPickAnnouncementNotification).toHaveBeenCalledWith(
|
|
expect.objectContaining({ nextOwnerDiscordId: undefined })
|
|
);
|
|
});
|
|
|
|
it("omits nextOwnerDiscordId when nextTeamOwnerId is null", async () => {
|
|
const db = makeMockDb();
|
|
|
|
await notifyPickMadeOnDiscord({ ...BASE_PARAMS, nextTeamOwnerId: null, db: db as never });
|
|
|
|
expect(findDiscordIdsByUserIds).not.toHaveBeenCalled();
|
|
expect(sendPickAnnouncementNotification).toHaveBeenCalledWith(
|
|
expect.objectContaining({ nextTeamName: "Beta United", nextOwnerDiscordId: undefined })
|
|
);
|
|
});
|
|
|
|
it("skips owner lookup and passes isDraftComplete: true when draft is done", async () => {
|
|
const db = makeMockDb();
|
|
|
|
await notifyPickMadeOnDiscord({ ...BASE_PARAMS, isDraftComplete: true, db: db as never });
|
|
|
|
expect(db.query.users.findFirst).not.toHaveBeenCalled();
|
|
expect(sendPickAnnouncementNotification).toHaveBeenCalledWith(
|
|
expect.objectContaining({ isDraftComplete: true })
|
|
);
|
|
});
|
|
|
|
it("suppresses errors thrown by sendPickAnnouncementNotification — caller's .catch handles them", async () => {
|
|
vi.mocked(sendPickAnnouncementNotification).mockRejectedValue(new Error("webhook down"));
|
|
const db = makeMockDb();
|
|
|
|
// The function itself does NOT swallow errors; the caller chains .catch().
|
|
await expect(
|
|
notifyPickMadeOnDiscord({ ...BASE_PARAMS, db: db as never })
|
|
).rejects.toThrow("webhook down");
|
|
});
|
|
|
|
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
|
|
// Discord should say "Round 2, Pick 9" not "Round 2, Pick 5"
|
|
const db = makeMockDb();
|
|
|
|
await notifyPickMadeOnDiscord({
|
|
...BASE_PARAMS,
|
|
pickNumber: 22,
|
|
round: 2,
|
|
pickInRound: 9,
|
|
db: db as never,
|
|
});
|
|
|
|
expect(sendPickAnnouncementNotification).toHaveBeenCalledWith(
|
|
expect.objectContaining({ pickNumber: 22, round: 2, pickInRound: 9 })
|
|
);
|
|
});
|
|
});
|