mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-09 04:02:40 -05:00
292 lines
6.8 KiB
TypeScript
292 lines
6.8 KiB
TypeScript
import { add } from "date-fns";
|
|
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
import { db } from "~/db/sql";
|
|
import { RunningTournaments } from "~/features/tournament-bracket/core/RunningTournaments.server";
|
|
import {
|
|
testTournament,
|
|
tournamentCtxTeam,
|
|
} from "~/features/tournament-bracket/core/tests/test-utils";
|
|
import { dbReset } from "~/utils/Test";
|
|
import { SyncLiveStreamsRoutine } from "./syncLiveStreams";
|
|
|
|
const { mockGetStreams } = vi.hoisted(() => ({
|
|
mockGetStreams: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("~/modules/twitch", () => ({
|
|
getStreams: mockGetStreams,
|
|
}));
|
|
|
|
vi.mock("~/modules/twitch/utils", () => ({
|
|
hasTwitchEnvVars: () => true,
|
|
}));
|
|
|
|
vi.mock("~/features/user-page/UserRepository.server", () => ({
|
|
findIdsByTwitchUsernames: () => [],
|
|
}));
|
|
|
|
function findAllTournamentStreamers() {
|
|
return db.selectFrom("TournamentStreamer").selectAll().execute();
|
|
}
|
|
|
|
function findAllLiveStreams() {
|
|
return db.selectFrom("LiveStream").selectAll().execute();
|
|
}
|
|
|
|
function addRunningTournament(
|
|
ctx?: Partial<Parameters<typeof testTournament>[0]["ctx"]>,
|
|
) {
|
|
const tournament = testTournament({ ctx });
|
|
RunningTournaments.add(tournament);
|
|
return tournament;
|
|
}
|
|
|
|
let timeOffset = 0;
|
|
|
|
describe("syncLiveStreams tournament streamers", () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
vi.setSystemTime(
|
|
add(new Date("2025-01-15T12:00:00Z"), { minutes: timeOffset }),
|
|
);
|
|
timeOffset += 31;
|
|
dbReset();
|
|
RunningTournaments.clear();
|
|
mockGetStreams.mockReset();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
test("populates LiveStream table with streams", async () => {
|
|
mockGetStreams.mockResolvedValue([
|
|
{
|
|
twitchUserName: "streamer_one",
|
|
viewerCount: 100,
|
|
thumbnailUrl: "https://thumb.jpg",
|
|
},
|
|
{
|
|
twitchUserName: "streamer_two",
|
|
viewerCount: 50,
|
|
thumbnailUrl: "https://thumb2.jpg",
|
|
},
|
|
]);
|
|
|
|
await SyncLiveStreamsRoutine.run();
|
|
|
|
const rows = await findAllLiveStreams();
|
|
expect(rows).toHaveLength(2);
|
|
expect(rows.map((r) => r.twitch).sort()).toEqual([
|
|
"streamer_one",
|
|
"streamer_two",
|
|
]);
|
|
expect(rows[0].viewerCount).toBe(100);
|
|
});
|
|
|
|
test("inserts streamer rows for players who are live", async () => {
|
|
mockGetStreams.mockResolvedValue([
|
|
{ twitchUserName: "player_one", viewerCount: 100, thumbnailUrl: "" },
|
|
]);
|
|
|
|
addRunningTournament({
|
|
teams: [
|
|
tournamentCtxTeam(1, {
|
|
members: [
|
|
{
|
|
userId: 10,
|
|
username: "Player One",
|
|
discordId: "10",
|
|
discordAvatar: null,
|
|
customUrl: null,
|
|
inGameName: null,
|
|
country: null,
|
|
twitch: "player_one",
|
|
plusTier: null,
|
|
role: "OWNER",
|
|
createdAt: 0,
|
|
streamTwitch: null,
|
|
streamViewerCount: null,
|
|
streamThumbnailUrl: null,
|
|
},
|
|
],
|
|
}),
|
|
],
|
|
});
|
|
|
|
await SyncLiveStreamsRoutine.run();
|
|
|
|
const rows = await findAllTournamentStreamers();
|
|
expect(rows).toHaveLength(1);
|
|
expect(rows[0].userId).toBe(10);
|
|
expect(rows[0].twitchAccount).toBe("player_one");
|
|
expect(rows[0].tournamentId).toBe(1);
|
|
});
|
|
|
|
test("skips dropped-out teams", async () => {
|
|
mockGetStreams.mockResolvedValue([
|
|
{ twitchUserName: "dropped_player", viewerCount: 50, thumbnailUrl: "" },
|
|
]);
|
|
|
|
addRunningTournament({
|
|
teams: [
|
|
tournamentCtxTeam(1, {
|
|
droppedOut: 1,
|
|
members: [
|
|
{
|
|
userId: 20,
|
|
username: "Dropped",
|
|
discordId: "20",
|
|
discordAvatar: null,
|
|
customUrl: null,
|
|
inGameName: null,
|
|
country: null,
|
|
twitch: "dropped_player",
|
|
plusTier: null,
|
|
role: "OWNER",
|
|
createdAt: 0,
|
|
streamTwitch: null,
|
|
streamViewerCount: null,
|
|
streamThumbnailUrl: null,
|
|
},
|
|
],
|
|
}),
|
|
],
|
|
});
|
|
|
|
await SyncLiveStreamsRoutine.run();
|
|
|
|
const rows = await findAllTournamentStreamers();
|
|
expect(rows).toHaveLength(0);
|
|
});
|
|
|
|
test("inserts cast accounts with null userId", async () => {
|
|
mockGetStreams.mockResolvedValue([
|
|
{ twitchUserName: "caster_account", viewerCount: 200, thumbnailUrl: "" },
|
|
]);
|
|
|
|
addRunningTournament({
|
|
castTwitchAccounts: ["caster_account"],
|
|
});
|
|
|
|
await SyncLiveStreamsRoutine.run();
|
|
|
|
const rows = await findAllTournamentStreamers();
|
|
expect(rows).toHaveLength(1);
|
|
expect(rows[0].userId).toBeNull();
|
|
expect(rows[0].twitchAccount).toBe("caster_account");
|
|
});
|
|
|
|
test("no inserts when no streams match", async () => {
|
|
mockGetStreams.mockResolvedValue([
|
|
{ twitchUserName: "unrelated_stream", viewerCount: 10, thumbnailUrl: "" },
|
|
]);
|
|
|
|
addRunningTournament({
|
|
teams: [
|
|
tournamentCtxTeam(1, {
|
|
members: [
|
|
{
|
|
userId: 30,
|
|
username: "No Match",
|
|
discordId: "30",
|
|
discordAvatar: null,
|
|
customUrl: null,
|
|
inGameName: null,
|
|
country: null,
|
|
twitch: "different_account",
|
|
plusTier: null,
|
|
role: "OWNER",
|
|
createdAt: 0,
|
|
streamTwitch: null,
|
|
streamViewerCount: null,
|
|
streamThumbnailUrl: null,
|
|
},
|
|
],
|
|
}),
|
|
],
|
|
});
|
|
|
|
await SyncLiveStreamsRoutine.run();
|
|
|
|
const rows = await findAllTournamentStreamers();
|
|
expect(rows).toHaveLength(0);
|
|
});
|
|
|
|
test("throttles to run only every 30 minutes", async () => {
|
|
mockGetStreams.mockResolvedValue([
|
|
{ twitchUserName: "streamer_a", viewerCount: 100, thumbnailUrl: "" },
|
|
]);
|
|
|
|
addRunningTournament({
|
|
teams: [
|
|
tournamentCtxTeam(1, {
|
|
members: [
|
|
{
|
|
userId: 40,
|
|
username: "Streamer A",
|
|
discordId: "40",
|
|
discordAvatar: null,
|
|
customUrl: null,
|
|
inGameName: null,
|
|
country: null,
|
|
twitch: "streamer_a",
|
|
plusTier: null,
|
|
role: "OWNER",
|
|
createdAt: 0,
|
|
streamTwitch: null,
|
|
streamViewerCount: null,
|
|
streamThumbnailUrl: null,
|
|
},
|
|
],
|
|
}),
|
|
],
|
|
});
|
|
|
|
await SyncLiveStreamsRoutine.run();
|
|
|
|
const rowsAfterFirst = await findAllTournamentStreamers();
|
|
expect(rowsAfterFirst).toHaveLength(1);
|
|
|
|
// clear DB and add a different tournament — if throttle works, nothing new is inserted
|
|
await db.deleteFrom("TournamentStreamer").execute();
|
|
RunningTournaments.clear();
|
|
|
|
mockGetStreams.mockResolvedValue([
|
|
{ twitchUserName: "streamer_b", viewerCount: 50, thumbnailUrl: "" },
|
|
]);
|
|
|
|
addRunningTournament({
|
|
id: 2,
|
|
teams: [
|
|
tournamentCtxTeam(2, {
|
|
members: [
|
|
{
|
|
userId: 50,
|
|
username: "Streamer B",
|
|
discordId: "50",
|
|
discordAvatar: null,
|
|
customUrl: null,
|
|
inGameName: null,
|
|
country: null,
|
|
twitch: "streamer_b",
|
|
plusTier: null,
|
|
role: "OWNER",
|
|
createdAt: 0,
|
|
streamTwitch: null,
|
|
streamViewerCount: null,
|
|
streamThumbnailUrl: null,
|
|
},
|
|
],
|
|
}),
|
|
],
|
|
});
|
|
|
|
// call again without advancing time — should be throttled
|
|
await SyncLiveStreamsRoutine.run();
|
|
|
|
const rowsAfterSecond = await findAllTournamentStreamers();
|
|
expect(rowsAfterSecond).toHaveLength(0);
|
|
});
|
|
});
|