- Replace useFetcher with direct fetch() for add/remove/reorder so rapid
clicks no longer cancel in-flight requests
- Add toast.error() on all failure paths (matching live draft room pattern)
and revert optimistic state when operations fail
- Add useRef to give handleReorder a stable reference without a localQueue
closure dependency, preventing unnecessary QueueSection re-renders
- Convert allPlayersPanel and rankingsPanel to useMemo
- Remove leagueId from loader return; read from useParams() instead
- Extract queueBuilderHref to a variable in the league home component
- Add emptyMessage prop to QueueSection with a correct message for the
pre-draft context ("Add players from All Players...")
- Add draft-queue-access.test.ts covering all loader access control paths:
401/403 errors, status-based redirects, and redirect URL correctness
https://claude.ai/code/session_01Gu2DkTWL3nv74EMuGPpxhG
213 lines
6.6 KiB
TypeScript
213 lines
6.6 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const createMockSeason = (overrides: Partial<{
|
|
id: string;
|
|
leagueId: string;
|
|
status: 'pre_draft' | 'draft' | 'active' | 'completed';
|
|
}> = {}) => ({
|
|
id: 'season-1',
|
|
leagueId: 'league-1',
|
|
year: 2025,
|
|
status: 'pre_draft' as const,
|
|
templateId: null,
|
|
draftRounds: 20,
|
|
flexSpots: 0,
|
|
draftDateTime: null,
|
|
draftInitialTime: 120,
|
|
draftIncrementTime: 15,
|
|
currentPickNumber: null,
|
|
draftStartedAt: null,
|
|
draftPaused: false,
|
|
inviteCode: 'TEST123',
|
|
pointsFor1st: 100,
|
|
pointsFor2nd: 70,
|
|
pointsFor3rd: 50,
|
|
pointsFor4th: 40,
|
|
pointsFor5th: 25,
|
|
pointsFor6th: 25,
|
|
pointsFor7th: 15,
|
|
pointsFor8th: 15,
|
|
createdAt: new Date('2025-01-01'),
|
|
updatedAt: new Date('2025-01-01'),
|
|
...overrides,
|
|
});
|
|
|
|
type MockSeason = ReturnType<typeof createMockSeason>;
|
|
|
|
// Mirrors the access-control and redirect logic from the pre-draft rankings loader.
|
|
// Returns null for a successful load, a redirect URL, or an error object.
|
|
function checkPreDraftRankingsAccess(opts: {
|
|
leagueId: string;
|
|
season: MockSeason;
|
|
userId: string | null;
|
|
hasTeam: boolean;
|
|
}): { redirect: string } | { status: number; message: string } | null {
|
|
const { leagueId, season, userId, hasTeam } = opts;
|
|
const { id: seasonId } = season;
|
|
|
|
if (season.status === 'draft') {
|
|
return { redirect: `/leagues/${leagueId}/draft/${seasonId}` };
|
|
}
|
|
if (season.status === 'active' || season.status === 'completed') {
|
|
return { redirect: `/leagues/${leagueId}/draft-board/${seasonId}` };
|
|
}
|
|
|
|
if (!userId) {
|
|
return { status: 401, message: 'You must be logged in to set pre-draft rankings' };
|
|
}
|
|
|
|
if (!hasTeam) {
|
|
return { status: 403, message: 'You do not have a team in this season' };
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Redirect behaviour based on season status
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('Pre-Draft Rankings Access - Season Status Redirects', () => {
|
|
it('redirects to the draft room when status is draft', () => {
|
|
const season = createMockSeason({ status: 'draft' });
|
|
const result = checkPreDraftRankingsAccess({
|
|
leagueId: 'league-1',
|
|
season,
|
|
userId: 'user-1',
|
|
hasTeam: true,
|
|
});
|
|
expect(result).toEqual({ redirect: '/leagues/league-1/draft/season-1' });
|
|
});
|
|
|
|
it('redirects to the draft board when status is active', () => {
|
|
const season = createMockSeason({ status: 'active' });
|
|
const result = checkPreDraftRankingsAccess({
|
|
leagueId: 'league-1',
|
|
season,
|
|
userId: 'user-1',
|
|
hasTeam: true,
|
|
});
|
|
expect(result).toEqual({ redirect: '/leagues/league-1/draft-board/season-1' });
|
|
});
|
|
|
|
it('redirects to the draft board when status is completed', () => {
|
|
const season = createMockSeason({ status: 'completed' });
|
|
const result = checkPreDraftRankingsAccess({
|
|
leagueId: 'league-1',
|
|
season,
|
|
userId: 'user-1',
|
|
hasTeam: true,
|
|
});
|
|
expect(result).toEqual({ redirect: '/leagues/league-1/draft-board/season-1' });
|
|
});
|
|
|
|
it('does not redirect when status is pre_draft', () => {
|
|
const season = createMockSeason({ status: 'pre_draft' });
|
|
const result = checkPreDraftRankingsAccess({
|
|
leagueId: 'league-1',
|
|
season,
|
|
userId: 'user-1',
|
|
hasTeam: true,
|
|
});
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Authentication
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('Pre-Draft Rankings Access - Authentication', () => {
|
|
it('returns 401 when no user is logged in', () => {
|
|
const season = createMockSeason({ status: 'pre_draft' });
|
|
const result = checkPreDraftRankingsAccess({
|
|
leagueId: 'league-1',
|
|
season,
|
|
userId: null,
|
|
hasTeam: false,
|
|
});
|
|
expect(result).toMatchObject({ status: 401 });
|
|
});
|
|
|
|
it('uses a descriptive 401 message', () => {
|
|
const season = createMockSeason({ status: 'pre_draft' });
|
|
const result = checkPreDraftRankingsAccess({
|
|
leagueId: 'league-1',
|
|
season,
|
|
userId: null,
|
|
hasTeam: false,
|
|
});
|
|
expect(result).toMatchObject({ message: 'You must be logged in to set pre-draft rankings' });
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Team membership
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('Pre-Draft Rankings Access - Team Membership', () => {
|
|
it('returns 403 for a logged-in user with no team in the season', () => {
|
|
const season = createMockSeason({ status: 'pre_draft' });
|
|
const result = checkPreDraftRankingsAccess({
|
|
leagueId: 'league-1',
|
|
season,
|
|
userId: 'outsider',
|
|
hasTeam: false,
|
|
});
|
|
expect(result).toMatchObject({ status: 403 });
|
|
});
|
|
|
|
it('uses a descriptive 403 message', () => {
|
|
const season = createMockSeason({ status: 'pre_draft' });
|
|
const result = checkPreDraftRankingsAccess({
|
|
leagueId: 'league-1',
|
|
season,
|
|
userId: 'outsider',
|
|
hasTeam: false,
|
|
});
|
|
expect(result).toMatchObject({ message: 'You do not have a team in this season' });
|
|
});
|
|
|
|
it('grants access to a logged-in user who owns a team', () => {
|
|
const season = createMockSeason({ status: 'pre_draft' });
|
|
const result = checkPreDraftRankingsAccess({
|
|
leagueId: 'league-1',
|
|
season,
|
|
userId: 'owner-user',
|
|
hasTeam: true,
|
|
});
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Redirect targets use the correct leagueId and seasonId
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('Pre-Draft Rankings Access - Redirect URL Correctness', () => {
|
|
it('uses the leagueId from params in the redirect URL', () => {
|
|
const season = createMockSeason({ id: 'season-99', status: 'draft' });
|
|
const result = checkPreDraftRankingsAccess({
|
|
leagueId: 'league-42',
|
|
season,
|
|
userId: 'user-1',
|
|
hasTeam: true,
|
|
});
|
|
expect(result).toEqual({ redirect: '/leagues/league-42/draft/season-99' });
|
|
});
|
|
|
|
it('uses the seasonId in the redirect URL', () => {
|
|
const season = createMockSeason({ id: 'season-77', status: 'active' });
|
|
const result = checkPreDraftRankingsAccess({
|
|
leagueId: 'league-1',
|
|
season,
|
|
userId: 'user-1',
|
|
hasTeam: true,
|
|
});
|
|
expect(result).toEqual({ redirect: '/leagues/league-1/draft-board/season-77' });
|
|
});
|
|
});
|