mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-07 03:35:40 -05:00
Check tournament ids valid before persisting seeds
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import * as TournamentFactory from "~/db/seed/factories/TournamentFactory";
|
||||
import * as TournamentTeamFactory from "~/db/seed/factories/TournamentTeamFactory";
|
||||
import * as UserFactory from "~/db/seed/factories/UserFactory";
|
||||
import { db } from "~/db/sql";
|
||||
import { wrappedAction } from "~/utils/Test";
|
||||
import type { adminSeedsActionSchema } from "../tournament-admin-schemas";
|
||||
import { action } from "./to.$id.admin.seeds.server";
|
||||
|
||||
const seedsAction = wrappedAction<typeof adminSeedsActionSchema>({ action });
|
||||
|
||||
const teamRow = (tournamentTeamId: number) =>
|
||||
db
|
||||
.selectFrom("TournamentTeam")
|
||||
.select(["id", "tournamentId", "seed", "startingBracketIdx"])
|
||||
.where("id", "=", tournamentTeamId)
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
describe("tournament seeds admin action", () => {
|
||||
test("does not touch the seeds of a team of another tournament", async () => {
|
||||
await UserFactory.createAdmin();
|
||||
const attacker = await UserFactory.createRegular();
|
||||
const victimTo = await UserFactory.create();
|
||||
const [player] = await UserFactory.createMany(1);
|
||||
|
||||
// the attacker organizes a tournament of their own
|
||||
const ownTournament = await TournamentFactory.create({
|
||||
authorId: attacker.id,
|
||||
});
|
||||
|
||||
// somebody else's tournament, with a team they seeded themselves
|
||||
const otherTournament = await TournamentFactory.create({
|
||||
authorId: victimTo.id,
|
||||
});
|
||||
const otherTeam = await TournamentTeamFactory.create({
|
||||
tournamentId: otherTournament.id,
|
||||
memberUserIds: [player.id],
|
||||
});
|
||||
|
||||
await seedsAction(
|
||||
{ _action: "UPDATE_SEEDS", seeds: JSON.stringify([otherTeam.id]) as any },
|
||||
{ user: "regular", params: { id: String(ownTournament.id) } },
|
||||
);
|
||||
|
||||
const after = await teamRow(otherTeam.id);
|
||||
expect(after.tournamentId).toBe(otherTournament.id);
|
||||
expect(after.seed, "another tournament's team got seeded").toBeNull();
|
||||
});
|
||||
|
||||
test("does not touch the starting bracket of a team of another tournament", async () => {
|
||||
await UserFactory.createAdmin();
|
||||
const attacker = await UserFactory.createRegular();
|
||||
const victimTo = await UserFactory.create();
|
||||
const [player] = await UserFactory.createMany(1);
|
||||
|
||||
const ownTournament = await TournamentFactory.create({
|
||||
authorId: attacker.id,
|
||||
});
|
||||
|
||||
const otherTournament = await TournamentFactory.create({
|
||||
authorId: victimTo.id,
|
||||
});
|
||||
const otherTeam = await TournamentTeamFactory.create({
|
||||
tournamentId: otherTournament.id,
|
||||
memberUserIds: [player.id],
|
||||
});
|
||||
|
||||
await seedsAction(
|
||||
{
|
||||
_action: "UPDATE_STARTING_BRACKETS",
|
||||
startingBrackets: JSON.stringify([
|
||||
{ tournamentTeamId: otherTeam.id, startingBracketIdx: 0 },
|
||||
]) as any,
|
||||
},
|
||||
{ user: "regular", params: { id: String(ownTournament.id) } },
|
||||
);
|
||||
|
||||
const after = await teamRow(otherTeam.id);
|
||||
expect(
|
||||
after.startingBracketIdx,
|
||||
"another tournament's team got moved to a starting bracket",
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ActionFunction } from "react-router";
|
||||
import * as TournamentRepository from "~/features/tournament/TournamentRepository.server";
|
||||
import * as TournamentTeamRepository from "~/features/tournament/TournamentTeamRepository.server";
|
||||
import type { Tournament } from "~/features/tournament-bracket/core/Tournament";
|
||||
import {
|
||||
clearTournamentDataCache,
|
||||
requireTournamentOrganizer,
|
||||
@@ -30,6 +31,7 @@ export const action: ActionFunction = async ({ request, params }) => {
|
||||
case "UPDATE_SEEDS": {
|
||||
requireTournamentOrganizer(tournament, user);
|
||||
errorToastIfFalsy(!tournament.hasStarted, "Tournament has started");
|
||||
validateTeamsOfTournament(tournament, data.seeds);
|
||||
|
||||
await TournamentRepository.updateTeamSeeds({
|
||||
tournamentId,
|
||||
@@ -54,6 +56,10 @@ export const action: ActionFunction = async ({ request, params }) => {
|
||||
),
|
||||
"Invalid starting bracket idx",
|
||||
);
|
||||
validateTeamsOfTournament(
|
||||
tournament,
|
||||
data.startingBrackets.map((t) => t.tournamentTeamId),
|
||||
);
|
||||
|
||||
await TournamentTeamRepository.updateStartingBrackets(
|
||||
data.startingBrackets,
|
||||
@@ -73,10 +79,9 @@ export const action: ActionFunction = async ({ request, params }) => {
|
||||
"No starting bracket has A/B divisions enabled",
|
||||
);
|
||||
|
||||
const validTeamIds = new Set(tournament.ctx.teams.map((t) => t.id));
|
||||
errorToastIfFalsy(
|
||||
data.abDivisions.every((t) => validTeamIds.has(t.tournamentTeamId)),
|
||||
"Invalid tournament team id",
|
||||
validateTeamsOfTournament(
|
||||
tournament,
|
||||
data.abDivisions.map((t) => t.tournamentTeamId),
|
||||
);
|
||||
|
||||
await TournamentTeamRepository.updateAbDivisions(data.abDivisions);
|
||||
@@ -93,3 +98,15 @@ export const action: ActionFunction = async ({ request, params }) => {
|
||||
|
||||
return successToast(message);
|
||||
};
|
||||
|
||||
function validateTeamsOfTournament(
|
||||
tournament: Tournament,
|
||||
tournamentTeamIds: number[],
|
||||
) {
|
||||
const validTeamIds = new Set(tournament.ctx.teams.map((t) => t.id));
|
||||
|
||||
errorToastIfFalsy(
|
||||
tournamentTeamIds.every((id) => validTeamIds.has(id)),
|
||||
"Invalid tournament team id",
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user