2026-03-20 21:36:39 -07:00
|
|
|
/**
|
|
|
|
|
* Tests for draft.make-pick timer behavior across chess_clock and standard modes.
|
|
|
|
|
*
|
2026-03-24 17:00:32 -07:00
|
|
|
* chess_clock: after ANY pick (owner, commissioner, admin), bank += increment
|
|
|
|
|
* standard: after ANY pick, bank resets to exactly draftIncrementTime
|
2026-03-20 21:36:39 -07:00
|
|
|
*/
|
|
|
|
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
|
|
|
import type { RouterContextProvider } from "react-router";
|
|
|
|
|
import { action } from "~/routes/api/draft.make-pick";
|
|
|
|
|
|
|
|
|
|
const ctx = {} as unknown as RouterContextProvider;
|
|
|
|
|
|
|
|
|
|
vi.mock("~/database/context");
|
|
|
|
|
vi.mock("~/server/socket", () => ({
|
|
|
|
|
getSocketIO: vi.fn(),
|
2026-04-30 10:14:14 -07:00
|
|
|
scheduleDraftRoomClosure: vi.fn(),
|
2026-03-20 21:36:39 -07:00
|
|
|
}));
|
Migrate authentication from Clerk to BetterAuth (#324)
* Migrate authentication from Clerk to BetterAuth (#322)
Replaces @clerk/react-router with self-hosted better-auth to eliminate
the external Clerk dependency and keep all user/session data in our own
PostgreSQL database.
**What changed**
- New: auth.server.ts (BetterAuth config w/ Drizzle adapter, bcrypt, Resend), auth-client.ts, api.auth.$.ts handler
- New: /login and /register pages with email+password and Google/Discord OAuth; open-redirect guard on redirectTo param
- New: UserMenu component replacing Clerk's UserButton
- Schema: sessions, accounts, verifications tables; emailVerified column; clerkId made nullable
- Migrations 0081 (BetterAuth tables) and 0082 (accounts extra columns for v1.6.9)
- All ~30 route files: getAuth → auth.api.getSession, isUserAdminByClerkId → isUserAdmin
- root.tsx: isAdmin read directly from session.user.isAdmin (no extra DB query)
- useDraftAuthRecovery: removed Clerk JWT refresh logic; replaced with cookie-session check
- models/user.ts: removed findUserByClerkId, findOrCreateUser, updateUserByClerkId (webhook pattern)
- Deleted: app/routes/api/webhooks/clerk.ts; uninstalled @clerk/react-router, @clerk/themes, svix
- scripts/migrate.mjs: extended with idempotent Clerk → BetterAuth data migration (FK conversion, email_verified, OAuth accounts)
- scripts/migrate-clerk-passwords.mjs: one-time script to import bcrypt hashes from Clerk CSV export
- BETTERAUTH_MIGRATION.md: dev and production runbooks
- All test mocks updated: vi.mock('~/lib/auth.server') instead of @clerk/react-router/server
- Test fixtures: added emailVerified field
**Follow-up (post-stable)**
- Rename actor_clerk_id column → actor_user_id in commissioner_audit_log
- Drop clerk_id column from users once migration confirmed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add .npmrc with legacy-peer-deps for better-auth/drizzle peer dep conflict
better-auth@1.6.9 declares peerOptional deps on drizzle-orm ^0.45.2 and
drizzle-kit >=0.31.4, but we run drizzle-orm ~0.36.3 / drizzle-kit ~0.28.1.
The adapter works correctly at runtime with our versions — the peer dep is
only for stricter type checking. This unblocks npm ci in CI without a risky
drizzle major-version upgrade.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 22:00:49 -07:00
|
|
|
vi.mock("~/lib/auth.server", () => ({
|
|
|
|
|
auth: { api: { getSession: vi.fn() } },
|
2026-03-20 21:36:39 -07:00
|
|
|
}));
|
|
|
|
|
vi.mock("~/models/draft-pick", () => ({
|
|
|
|
|
getDraftPicksWithSports: vi.fn(),
|
|
|
|
|
getTeamDraftPicksWithSports: vi.fn(),
|
|
|
|
|
}));
|
|
|
|
|
vi.mock("~/models/participant", () => ({
|
|
|
|
|
getParticipantsForSeasonWithSports: vi.fn(),
|
|
|
|
|
}));
|
|
|
|
|
vi.mock("~/models/season-sport", () => ({
|
|
|
|
|
getSeasonSportsSimple: vi.fn(),
|
|
|
|
|
}));
|
|
|
|
|
vi.mock("~/lib/draft-eligibility", () => ({
|
|
|
|
|
calculateDraftEligibility: vi.fn(),
|
|
|
|
|
}));
|
|
|
|
|
vi.mock("~/models/draft-utils", () => ({
|
|
|
|
|
checkAndTriggerNextAutodraft: vi.fn(),
|
|
|
|
|
calculatePickInfo: vi.fn().mockReturnValue({ round: 1, pickInRound: 1, teamIndex: 0 }),
|
|
|
|
|
pruneIneligibleQueueItems: vi.fn().mockResolvedValue([]),
|
|
|
|
|
}));
|
|
|
|
|
vi.mock("~/models/user", () => ({
|
Migrate authentication from Clerk to BetterAuth (#324)
* Migrate authentication from Clerk to BetterAuth (#322)
Replaces @clerk/react-router with self-hosted better-auth to eliminate
the external Clerk dependency and keep all user/session data in our own
PostgreSQL database.
**What changed**
- New: auth.server.ts (BetterAuth config w/ Drizzle adapter, bcrypt, Resend), auth-client.ts, api.auth.$.ts handler
- New: /login and /register pages with email+password and Google/Discord OAuth; open-redirect guard on redirectTo param
- New: UserMenu component replacing Clerk's UserButton
- Schema: sessions, accounts, verifications tables; emailVerified column; clerkId made nullable
- Migrations 0081 (BetterAuth tables) and 0082 (accounts extra columns for v1.6.9)
- All ~30 route files: getAuth → auth.api.getSession, isUserAdminByClerkId → isUserAdmin
- root.tsx: isAdmin read directly from session.user.isAdmin (no extra DB query)
- useDraftAuthRecovery: removed Clerk JWT refresh logic; replaced with cookie-session check
- models/user.ts: removed findUserByClerkId, findOrCreateUser, updateUserByClerkId (webhook pattern)
- Deleted: app/routes/api/webhooks/clerk.ts; uninstalled @clerk/react-router, @clerk/themes, svix
- scripts/migrate.mjs: extended with idempotent Clerk → BetterAuth data migration (FK conversion, email_verified, OAuth accounts)
- scripts/migrate-clerk-passwords.mjs: one-time script to import bcrypt hashes from Clerk CSV export
- BETTERAUTH_MIGRATION.md: dev and production runbooks
- All test mocks updated: vi.mock('~/lib/auth.server') instead of @clerk/react-router/server
- Test fixtures: added emailVerified field
**Follow-up (post-stable)**
- Rename actor_clerk_id column → actor_user_id in commissioner_audit_log
- Drop clerk_id column from users once migration confirmed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add .npmrc with legacy-peer-deps for better-auth/drizzle peer dep conflict
better-auth@1.6.9 declares peerOptional deps on drizzle-orm ^0.45.2 and
drizzle-kit >=0.31.4, but we run drizzle-orm ~0.36.3 / drizzle-kit ~0.28.1.
The adapter works correctly at runtime with our versions — the peer dep is
only for stricter type checking. This unblocks npm ci in CI without a risky
drizzle major-version upgrade.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 22:00:49 -07:00
|
|
|
isUserAdmin: vi.fn(),
|
2026-03-20 21:36:39 -07:00
|
|
|
}));
|
|
|
|
|
|
2026-03-24 17:00:32 -07:00
|
|
|
// ── Fixtures ──────────────────────────────────────────────────────────────────
|
2026-03-20 21:36:39 -07:00
|
|
|
|
|
|
|
|
const SEASON_ID = "season-1";
|
|
|
|
|
const TEAM_ID = "team-1";
|
|
|
|
|
const NEXT_TEAM_ID = "team-2";
|
|
|
|
|
const PARTICIPANT_ID = "participant-1";
|
|
|
|
|
const OWNER_ID = "owner-user-1";
|
2026-03-24 17:00:32 -07:00
|
|
|
const COMMISSIONER_ID = "commissioner-user-1";
|
|
|
|
|
const ADMIN_ID = "admin-user-1";
|
2026-03-20 21:36:39 -07:00
|
|
|
const SPORT_ID = "sport-nfl";
|
|
|
|
|
|
|
|
|
|
const mockParticipant = {
|
|
|
|
|
id: PARTICIPANT_ID,
|
|
|
|
|
name: "Patrick Mahomes",
|
|
|
|
|
sportsSeason: {
|
|
|
|
|
id: "sports-season-1",
|
|
|
|
|
sport: { id: SPORT_ID, name: "NFL" },
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const mockDraftPick = {
|
|
|
|
|
id: "pick-1",
|
|
|
|
|
seasonId: SEASON_ID,
|
|
|
|
|
teamId: TEAM_ID,
|
|
|
|
|
participantId: PARTICIPANT_ID,
|
|
|
|
|
pickNumber: 1,
|
|
|
|
|
round: 1,
|
|
|
|
|
pickInRound: 1,
|
|
|
|
|
pickedByUserId: OWNER_ID,
|
|
|
|
|
pickedByType: "owner",
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-24 17:00:32 -07:00
|
|
|
// Two-team draft; team-1 picks first.
|
2026-03-20 21:36:39 -07:00
|
|
|
const mockDraftSlots = [
|
|
|
|
|
{
|
|
|
|
|
id: "slot-1",
|
|
|
|
|
seasonId: SEASON_ID,
|
|
|
|
|
teamId: TEAM_ID,
|
|
|
|
|
draftOrder: 1,
|
|
|
|
|
team: { id: TEAM_ID, name: "Team 1", seasonId: SEASON_ID, ownerId: OWNER_ID },
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "slot-2",
|
|
|
|
|
seasonId: SEASON_ID,
|
|
|
|
|
teamId: NEXT_TEAM_ID,
|
|
|
|
|
draftOrder: 2,
|
|
|
|
|
team: { id: NEXT_TEAM_ID, name: "Team 2", seasonId: SEASON_ID, ownerId: "owner-2" },
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
function makeSeason(overrides: Record<string, unknown> = {}) {
|
|
|
|
|
return {
|
|
|
|
|
id: SEASON_ID,
|
|
|
|
|
leagueId: "league-1",
|
|
|
|
|
status: "draft",
|
|
|
|
|
draftRounds: 3,
|
|
|
|
|
draftInitialTime: 120,
|
|
|
|
|
draftIncrementTime: 30,
|
|
|
|
|
draftTimerMode: "chess_clock",
|
|
|
|
|
currentPickNumber: 1,
|
|
|
|
|
draftPaused: false,
|
|
|
|
|
...overrides,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function makeRequest() {
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
formData.append("seasonId", SEASON_ID);
|
|
|
|
|
formData.append("participantId", PARTICIPANT_ID);
|
|
|
|
|
return new Request("http://localhost/api/draft/make-pick", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: formData,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-24 17:00:32 -07:00
|
|
|
// ── Tests ──────────────────────────────────────────────────────────────────────
|
2026-03-20 21:36:39 -07:00
|
|
|
|
|
|
|
|
describe("draft.make-pick action — timer mode behavior", () => {
|
|
|
|
|
let mockDb: any;
|
|
|
|
|
let mockSocketIO: any;
|
|
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
vi.clearAllMocks();
|
|
|
|
|
|
Migrate authentication from Clerk to BetterAuth (#324)
* Migrate authentication from Clerk to BetterAuth (#322)
Replaces @clerk/react-router with self-hosted better-auth to eliminate
the external Clerk dependency and keep all user/session data in our own
PostgreSQL database.
**What changed**
- New: auth.server.ts (BetterAuth config w/ Drizzle adapter, bcrypt, Resend), auth-client.ts, api.auth.$.ts handler
- New: /login and /register pages with email+password and Google/Discord OAuth; open-redirect guard on redirectTo param
- New: UserMenu component replacing Clerk's UserButton
- Schema: sessions, accounts, verifications tables; emailVerified column; clerkId made nullable
- Migrations 0081 (BetterAuth tables) and 0082 (accounts extra columns for v1.6.9)
- All ~30 route files: getAuth → auth.api.getSession, isUserAdminByClerkId → isUserAdmin
- root.tsx: isAdmin read directly from session.user.isAdmin (no extra DB query)
- useDraftAuthRecovery: removed Clerk JWT refresh logic; replaced with cookie-session check
- models/user.ts: removed findUserByClerkId, findOrCreateUser, updateUserByClerkId (webhook pattern)
- Deleted: app/routes/api/webhooks/clerk.ts; uninstalled @clerk/react-router, @clerk/themes, svix
- scripts/migrate.mjs: extended with idempotent Clerk → BetterAuth data migration (FK conversion, email_verified, OAuth accounts)
- scripts/migrate-clerk-passwords.mjs: one-time script to import bcrypt hashes from Clerk CSV export
- BETTERAUTH_MIGRATION.md: dev and production runbooks
- All test mocks updated: vi.mock('~/lib/auth.server') instead of @clerk/react-router/server
- Test fixtures: added emailVerified field
**Follow-up (post-stable)**
- Rename actor_clerk_id column → actor_user_id in commissioner_audit_log
- Drop clerk_id column from users once migration confirmed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add .npmrc with legacy-peer-deps for better-auth/drizzle peer dep conflict
better-auth@1.6.9 declares peerOptional deps on drizzle-orm ^0.45.2 and
drizzle-kit >=0.31.4, but we run drizzle-orm ~0.36.3 / drizzle-kit ~0.28.1.
The adapter works correctly at runtime with our versions — the peer dep is
only for stricter type checking. This unblocks npm ci in CI without a risky
drizzle major-version upgrade.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 22:00:49 -07:00
|
|
|
const { auth } = await import("~/lib/auth.server");
|
|
|
|
|
vi.mocked(auth.api.getSession).mockResolvedValue({ user: { id: OWNER_ID } } as any);
|
2026-03-20 21:36:39 -07:00
|
|
|
|
Migrate authentication from Clerk to BetterAuth (#324)
* Migrate authentication from Clerk to BetterAuth (#322)
Replaces @clerk/react-router with self-hosted better-auth to eliminate
the external Clerk dependency and keep all user/session data in our own
PostgreSQL database.
**What changed**
- New: auth.server.ts (BetterAuth config w/ Drizzle adapter, bcrypt, Resend), auth-client.ts, api.auth.$.ts handler
- New: /login and /register pages with email+password and Google/Discord OAuth; open-redirect guard on redirectTo param
- New: UserMenu component replacing Clerk's UserButton
- Schema: sessions, accounts, verifications tables; emailVerified column; clerkId made nullable
- Migrations 0081 (BetterAuth tables) and 0082 (accounts extra columns for v1.6.9)
- All ~30 route files: getAuth → auth.api.getSession, isUserAdminByClerkId → isUserAdmin
- root.tsx: isAdmin read directly from session.user.isAdmin (no extra DB query)
- useDraftAuthRecovery: removed Clerk JWT refresh logic; replaced with cookie-session check
- models/user.ts: removed findUserByClerkId, findOrCreateUser, updateUserByClerkId (webhook pattern)
- Deleted: app/routes/api/webhooks/clerk.ts; uninstalled @clerk/react-router, @clerk/themes, svix
- scripts/migrate.mjs: extended with idempotent Clerk → BetterAuth data migration (FK conversion, email_verified, OAuth accounts)
- scripts/migrate-clerk-passwords.mjs: one-time script to import bcrypt hashes from Clerk CSV export
- BETTERAUTH_MIGRATION.md: dev and production runbooks
- All test mocks updated: vi.mock('~/lib/auth.server') instead of @clerk/react-router/server
- Test fixtures: added emailVerified field
**Follow-up (post-stable)**
- Rename actor_clerk_id column → actor_user_id in commissioner_audit_log
- Drop clerk_id column from users once migration confirmed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add .npmrc with legacy-peer-deps for better-auth/drizzle peer dep conflict
better-auth@1.6.9 declares peerOptional deps on drizzle-orm ^0.45.2 and
drizzle-kit >=0.31.4, but we run drizzle-orm ~0.36.3 / drizzle-kit ~0.28.1.
The adapter works correctly at runtime with our versions — the peer dep is
only for stricter type checking. This unblocks npm ci in CI without a risky
drizzle major-version upgrade.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 22:00:49 -07:00
|
|
|
const { isUserAdmin } = await import("~/models/user");
|
|
|
|
|
vi.mocked(isUserAdmin).mockResolvedValue(false);
|
2026-03-20 21:36:39 -07:00
|
|
|
|
|
|
|
|
mockSocketIO = { to: vi.fn().mockReturnThis(), emit: vi.fn() };
|
|
|
|
|
const socketModule = await import("~/server/socket");
|
|
|
|
|
vi.mocked(socketModule.getSocketIO).mockReturnValue(mockSocketIO);
|
|
|
|
|
|
|
|
|
|
const { getDraftPicksWithSports, getTeamDraftPicksWithSports } = await import("~/models/draft-pick");
|
|
|
|
|
vi.mocked(getDraftPicksWithSports).mockResolvedValue([]);
|
|
|
|
|
vi.mocked(getTeamDraftPicksWithSports).mockResolvedValue([]);
|
|
|
|
|
|
|
|
|
|
const { getParticipantsForSeasonWithSports } = await import("~/models/participant");
|
|
|
|
|
vi.mocked(getParticipantsForSeasonWithSports).mockResolvedValue([]);
|
|
|
|
|
|
|
|
|
|
const { getSeasonSportsSimple } = await import("~/models/season-sport");
|
|
|
|
|
vi.mocked(getSeasonSportsSimple).mockResolvedValue([]);
|
|
|
|
|
|
|
|
|
|
const { calculateDraftEligibility } = await import("~/lib/draft-eligibility");
|
|
|
|
|
vi.mocked(calculateDraftEligibility).mockReturnValue({
|
|
|
|
|
eligibleSportIds: new Set([SPORT_ID]),
|
|
|
|
|
ineligibleReasons: {},
|
|
|
|
|
} as any);
|
|
|
|
|
|
|
|
|
|
const { checkAndTriggerNextAutodraft } = await import("~/models/draft-utils");
|
|
|
|
|
vi.mocked(checkAndTriggerNextAutodraft).mockResolvedValue(undefined);
|
|
|
|
|
|
|
|
|
|
mockDb = {
|
|
|
|
|
query: {
|
|
|
|
|
seasons: { findFirst: vi.fn() },
|
|
|
|
|
commissioners: { findFirst: vi.fn().mockResolvedValue(null) },
|
|
|
|
|
draftPicks: { findFirst: vi.fn().mockResolvedValue(null) },
|
|
|
|
|
participants: { findFirst: vi.fn().mockResolvedValue(mockParticipant) },
|
|
|
|
|
draftSlots: { findMany: vi.fn().mockResolvedValue(mockDraftSlots) },
|
2026-03-24 17:00:32 -07:00
|
|
|
draftTimers: { findFirst: vi.fn().mockResolvedValue({ id: "timer-1", timeRemaining: 75 }) },
|
2026-03-20 21:36:39 -07:00
|
|
|
draftQueue: { findMany: vi.fn().mockResolvedValue([]) },
|
|
|
|
|
},
|
|
|
|
|
insert: vi.fn().mockReturnThis(),
|
|
|
|
|
values: vi.fn().mockReturnThis(),
|
|
|
|
|
returning: vi.fn().mockResolvedValue([mockDraftPick]),
|
|
|
|
|
update: vi.fn().mockReturnThis(),
|
|
|
|
|
set: vi.fn().mockReturnThis(),
|
|
|
|
|
where: vi.fn().mockReturnThis(),
|
|
|
|
|
delete: vi.fn().mockReturnThis(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const { database } = await import("~/database/context");
|
|
|
|
|
vi.mocked(database).mockReturnValue(mockDb);
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-24 17:00:32 -07:00
|
|
|
// ── chess_clock mode ────────────────────────────────────────────────────────
|
|
|
|
|
|
2026-03-20 21:36:39 -07:00
|
|
|
describe("chess_clock mode", () => {
|
2026-03-24 17:00:32 -07:00
|
|
|
beforeEach(() => {
|
|
|
|
|
mockDb.query.seasons.findFirst.mockResolvedValue(
|
|
|
|
|
makeSeason({ draftTimerMode: "chess_clock", draftInitialTime: 120, draftIncrementTime: 30 })
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe("owner pick", () => {
|
|
|
|
|
// Auth default is OWNER_ID (team owner), no extra setup needed.
|
|
|
|
|
|
|
|
|
|
it("emits timer-update with bank + increment", async () => {
|
|
|
|
|
// DB returns 75 + 30 = 105 after the atomic add
|
2026-03-20 21:36:39 -07:00
|
|
|
mockDb.returning
|
2026-03-24 17:00:32 -07:00
|
|
|
.mockResolvedValueOnce([mockDraftPick])
|
|
|
|
|
.mockResolvedValueOnce([{ id: "timer-1", teamId: TEAM_ID, timeRemaining: 105 }]);
|
2026-03-20 21:36:39 -07:00
|
|
|
|
|
|
|
|
await action({ request: makeRequest(), params: {}, context: ctx });
|
|
|
|
|
|
|
|
|
|
expect(mockSocketIO.emit).toHaveBeenCalledWith(
|
|
|
|
|
"timer-update",
|
|
|
|
|
expect.objectContaining({ teamId: TEAM_ID, timeRemaining: 105 })
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-24 17:00:32 -07:00
|
|
|
it("accumulates a larger bank when more time was remaining", async () => {
|
|
|
|
|
mockDb.query.draftTimers.findFirst.mockResolvedValue({ id: "timer-1", timeRemaining: 100 });
|
|
|
|
|
// 100 + 30 = 130
|
2026-03-20 21:36:39 -07:00
|
|
|
mockDb.returning
|
|
|
|
|
.mockResolvedValueOnce([mockDraftPick])
|
2026-03-24 17:00:32 -07:00
|
|
|
.mockResolvedValueOnce([{ id: "timer-1", teamId: TEAM_ID, timeRemaining: 130 }]);
|
2026-03-20 21:36:39 -07:00
|
|
|
|
|
|
|
|
await action({ request: makeRequest(), params: {}, context: ctx });
|
|
|
|
|
|
|
|
|
|
expect(mockSocketIO.emit).toHaveBeenCalledWith(
|
|
|
|
|
"timer-update",
|
|
|
|
|
expect.objectContaining({ teamId: TEAM_ID, timeRemaining: 130 })
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-24 17:00:32 -07:00
|
|
|
it("writes the timer update to the DB", async () => {
|
|
|
|
|
mockDb.returning
|
|
|
|
|
.mockResolvedValueOnce([mockDraftPick])
|
|
|
|
|
.mockResolvedValueOnce([{ id: "timer-1", teamId: TEAM_ID, timeRemaining: 105 }]);
|
|
|
|
|
|
|
|
|
|
await action({ request: makeRequest(), params: {}, context: ctx });
|
2026-03-20 21:36:39 -07:00
|
|
|
|
2026-03-24 17:00:32 -07:00
|
|
|
expect(mockDb.set).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({ timeRemaining: expect.anything(), updatedAt: expect.any(Date) })
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe("commissioner pick", () => {
|
2026-03-20 21:36:39 -07:00
|
|
|
beforeEach(async () => {
|
Migrate authentication from Clerk to BetterAuth (#324)
* Migrate authentication from Clerk to BetterAuth (#322)
Replaces @clerk/react-router with self-hosted better-auth to eliminate
the external Clerk dependency and keep all user/session data in our own
PostgreSQL database.
**What changed**
- New: auth.server.ts (BetterAuth config w/ Drizzle adapter, bcrypt, Resend), auth-client.ts, api.auth.$.ts handler
- New: /login and /register pages with email+password and Google/Discord OAuth; open-redirect guard on redirectTo param
- New: UserMenu component replacing Clerk's UserButton
- Schema: sessions, accounts, verifications tables; emailVerified column; clerkId made nullable
- Migrations 0081 (BetterAuth tables) and 0082 (accounts extra columns for v1.6.9)
- All ~30 route files: getAuth → auth.api.getSession, isUserAdminByClerkId → isUserAdmin
- root.tsx: isAdmin read directly from session.user.isAdmin (no extra DB query)
- useDraftAuthRecovery: removed Clerk JWT refresh logic; replaced with cookie-session check
- models/user.ts: removed findUserByClerkId, findOrCreateUser, updateUserByClerkId (webhook pattern)
- Deleted: app/routes/api/webhooks/clerk.ts; uninstalled @clerk/react-router, @clerk/themes, svix
- scripts/migrate.mjs: extended with idempotent Clerk → BetterAuth data migration (FK conversion, email_verified, OAuth accounts)
- scripts/migrate-clerk-passwords.mjs: one-time script to import bcrypt hashes from Clerk CSV export
- BETTERAUTH_MIGRATION.md: dev and production runbooks
- All test mocks updated: vi.mock('~/lib/auth.server') instead of @clerk/react-router/server
- Test fixtures: added emailVerified field
**Follow-up (post-stable)**
- Rename actor_clerk_id column → actor_user_id in commissioner_audit_log
- Drop clerk_id column from users once migration confirmed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add .npmrc with legacy-peer-deps for better-auth/drizzle peer dep conflict
better-auth@1.6.9 declares peerOptional deps on drizzle-orm ^0.45.2 and
drizzle-kit >=0.31.4, but we run drizzle-orm ~0.36.3 / drizzle-kit ~0.28.1.
The adapter works correctly at runtime with our versions — the peer dep is
only for stricter type checking. This unblocks npm ci in CI without a risky
drizzle major-version upgrade.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 22:00:49 -07:00
|
|
|
const { auth } = await import("~/lib/auth.server");
|
|
|
|
|
vi.mocked(auth.api.getSession).mockResolvedValue({ user: { id: COMMISSIONER_ID } } as any);
|
2026-03-24 17:00:32 -07:00
|
|
|
|
|
|
|
|
mockDb.query.commissioners.findFirst.mockResolvedValue({ id: "c-1", userId: COMMISSIONER_ID });
|
|
|
|
|
// Commissioner does not own the team on the clock
|
2026-03-20 21:36:39 -07:00
|
|
|
mockDb.query.draftSlots.findMany.mockResolvedValue([
|
|
|
|
|
{
|
2026-03-24 17:00:32 -07:00
|
|
|
...mockDraftSlots[0],
|
|
|
|
|
team: { ...mockDraftSlots[0].team, ownerId: "someone-else" },
|
2026-03-20 21:36:39 -07:00
|
|
|
},
|
2026-03-24 17:00:32 -07:00
|
|
|
mockDraftSlots[1],
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("emits timer-update with bank + increment", async () => {
|
|
|
|
|
mockDb.returning
|
|
|
|
|
.mockResolvedValueOnce([mockDraftPick])
|
|
|
|
|
.mockResolvedValueOnce([{ id: "timer-1", teamId: TEAM_ID, timeRemaining: 105 }]);
|
|
|
|
|
|
|
|
|
|
await action({ request: makeRequest(), params: {}, context: ctx });
|
|
|
|
|
|
|
|
|
|
expect(mockSocketIO.emit).toHaveBeenCalledWith(
|
|
|
|
|
"timer-update",
|
|
|
|
|
expect.objectContaining({ teamId: TEAM_ID, timeRemaining: 105 })
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("writes the timer update to the DB", async () => {
|
|
|
|
|
mockDb.returning
|
|
|
|
|
.mockResolvedValueOnce([mockDraftPick])
|
|
|
|
|
.mockResolvedValueOnce([{ id: "timer-1", teamId: TEAM_ID, timeRemaining: 105 }]);
|
|
|
|
|
|
|
|
|
|
await action({ request: makeRequest(), params: {}, context: ctx });
|
|
|
|
|
|
|
|
|
|
expect(mockDb.set).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({ timeRemaining: expect.anything(), updatedAt: expect.any(Date) })
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe("admin pick", () => {
|
|
|
|
|
beforeEach(async () => {
|
Migrate authentication from Clerk to BetterAuth (#324)
* Migrate authentication from Clerk to BetterAuth (#322)
Replaces @clerk/react-router with self-hosted better-auth to eliminate
the external Clerk dependency and keep all user/session data in our own
PostgreSQL database.
**What changed**
- New: auth.server.ts (BetterAuth config w/ Drizzle adapter, bcrypt, Resend), auth-client.ts, api.auth.$.ts handler
- New: /login and /register pages with email+password and Google/Discord OAuth; open-redirect guard on redirectTo param
- New: UserMenu component replacing Clerk's UserButton
- Schema: sessions, accounts, verifications tables; emailVerified column; clerkId made nullable
- Migrations 0081 (BetterAuth tables) and 0082 (accounts extra columns for v1.6.9)
- All ~30 route files: getAuth → auth.api.getSession, isUserAdminByClerkId → isUserAdmin
- root.tsx: isAdmin read directly from session.user.isAdmin (no extra DB query)
- useDraftAuthRecovery: removed Clerk JWT refresh logic; replaced with cookie-session check
- models/user.ts: removed findUserByClerkId, findOrCreateUser, updateUserByClerkId (webhook pattern)
- Deleted: app/routes/api/webhooks/clerk.ts; uninstalled @clerk/react-router, @clerk/themes, svix
- scripts/migrate.mjs: extended with idempotent Clerk → BetterAuth data migration (FK conversion, email_verified, OAuth accounts)
- scripts/migrate-clerk-passwords.mjs: one-time script to import bcrypt hashes from Clerk CSV export
- BETTERAUTH_MIGRATION.md: dev and production runbooks
- All test mocks updated: vi.mock('~/lib/auth.server') instead of @clerk/react-router/server
- Test fixtures: added emailVerified field
**Follow-up (post-stable)**
- Rename actor_clerk_id column → actor_user_id in commissioner_audit_log
- Drop clerk_id column from users once migration confirmed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add .npmrc with legacy-peer-deps for better-auth/drizzle peer dep conflict
better-auth@1.6.9 declares peerOptional deps on drizzle-orm ^0.45.2 and
drizzle-kit >=0.31.4, but we run drizzle-orm ~0.36.3 / drizzle-kit ~0.28.1.
The adapter works correctly at runtime with our versions — the peer dep is
only for stricter type checking. This unblocks npm ci in CI without a risky
drizzle major-version upgrade.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 22:00:49 -07:00
|
|
|
const { auth } = await import("~/lib/auth.server");
|
|
|
|
|
vi.mocked(auth.api.getSession).mockResolvedValue({ user: { id: ADMIN_ID } } as any);
|
2026-03-24 17:00:32 -07:00
|
|
|
|
Migrate authentication from Clerk to BetterAuth (#324)
* Migrate authentication from Clerk to BetterAuth (#322)
Replaces @clerk/react-router with self-hosted better-auth to eliminate
the external Clerk dependency and keep all user/session data in our own
PostgreSQL database.
**What changed**
- New: auth.server.ts (BetterAuth config w/ Drizzle adapter, bcrypt, Resend), auth-client.ts, api.auth.$.ts handler
- New: /login and /register pages with email+password and Google/Discord OAuth; open-redirect guard on redirectTo param
- New: UserMenu component replacing Clerk's UserButton
- Schema: sessions, accounts, verifications tables; emailVerified column; clerkId made nullable
- Migrations 0081 (BetterAuth tables) and 0082 (accounts extra columns for v1.6.9)
- All ~30 route files: getAuth → auth.api.getSession, isUserAdminByClerkId → isUserAdmin
- root.tsx: isAdmin read directly from session.user.isAdmin (no extra DB query)
- useDraftAuthRecovery: removed Clerk JWT refresh logic; replaced with cookie-session check
- models/user.ts: removed findUserByClerkId, findOrCreateUser, updateUserByClerkId (webhook pattern)
- Deleted: app/routes/api/webhooks/clerk.ts; uninstalled @clerk/react-router, @clerk/themes, svix
- scripts/migrate.mjs: extended with idempotent Clerk → BetterAuth data migration (FK conversion, email_verified, OAuth accounts)
- scripts/migrate-clerk-passwords.mjs: one-time script to import bcrypt hashes from Clerk CSV export
- BETTERAUTH_MIGRATION.md: dev and production runbooks
- All test mocks updated: vi.mock('~/lib/auth.server') instead of @clerk/react-router/server
- Test fixtures: added emailVerified field
**Follow-up (post-stable)**
- Rename actor_clerk_id column → actor_user_id in commissioner_audit_log
- Drop clerk_id column from users once migration confirmed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add .npmrc with legacy-peer-deps for better-auth/drizzle peer dep conflict
better-auth@1.6.9 declares peerOptional deps on drizzle-orm ^0.45.2 and
drizzle-kit >=0.31.4, but we run drizzle-orm ~0.36.3 / drizzle-kit ~0.28.1.
The adapter works correctly at runtime with our versions — the peer dep is
only for stricter type checking. This unblocks npm ci in CI without a risky
drizzle major-version upgrade.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 22:00:49 -07:00
|
|
|
const { isUserAdmin } = await import("~/models/user");
|
|
|
|
|
vi.mocked(isUserAdmin).mockResolvedValue(true);
|
2026-03-24 17:00:32 -07:00
|
|
|
|
|
|
|
|
// Admin does not own the team on the clock
|
|
|
|
|
mockDb.query.draftSlots.findMany.mockResolvedValue([
|
2026-03-20 21:36:39 -07:00
|
|
|
{
|
2026-03-24 17:00:32 -07:00
|
|
|
...mockDraftSlots[0],
|
|
|
|
|
team: { ...mockDraftSlots[0].team, ownerId: "someone-else" },
|
2026-03-20 21:36:39 -07:00
|
|
|
},
|
2026-03-24 17:00:32 -07:00
|
|
|
mockDraftSlots[1],
|
2026-03-20 21:36:39 -07:00
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-24 17:00:32 -07:00
|
|
|
it("emits timer-update with bank + increment", async () => {
|
|
|
|
|
mockDb.returning
|
|
|
|
|
.mockResolvedValueOnce([mockDraftPick])
|
|
|
|
|
.mockResolvedValueOnce([{ id: "timer-1", teamId: TEAM_ID, timeRemaining: 105 }]);
|
2026-03-20 21:36:39 -07:00
|
|
|
|
|
|
|
|
await action({ request: makeRequest(), params: {}, context: ctx });
|
|
|
|
|
|
|
|
|
|
expect(mockSocketIO.emit).toHaveBeenCalledWith(
|
|
|
|
|
"timer-update",
|
2026-03-24 17:00:32 -07:00
|
|
|
expect.objectContaining({ teamId: TEAM_ID, timeRemaining: 105 })
|
2026-03-20 21:36:39 -07:00
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-24 17:00:32 -07:00
|
|
|
it("writes the timer update to the DB", async () => {
|
|
|
|
|
mockDb.returning
|
|
|
|
|
.mockResolvedValueOnce([mockDraftPick])
|
|
|
|
|
.mockResolvedValueOnce([{ id: "timer-1", teamId: TEAM_ID, timeRemaining: 105 }]);
|
2026-03-20 21:36:39 -07:00
|
|
|
|
|
|
|
|
await action({ request: makeRequest(), params: {}, context: ctx });
|
|
|
|
|
|
2026-03-24 17:00:32 -07:00
|
|
|
expect(mockDb.set).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({ timeRemaining: expect.anything(), updatedAt: expect.any(Date) })
|
|
|
|
|
);
|
2026-03-20 21:36:39 -07:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-24 17:00:32 -07:00
|
|
|
// ── standard mode ───────────────────────────────────────────────────────────
|
|
|
|
|
|
2026-03-20 21:36:39 -07:00
|
|
|
describe("standard mode", () => {
|
2026-03-24 17:00:32 -07:00
|
|
|
beforeEach(() => {
|
2026-03-20 21:36:39 -07:00
|
|
|
mockDb.query.seasons.findFirst.mockResolvedValue(
|
2026-03-24 17:00:32 -07:00
|
|
|
makeSeason({ draftTimerMode: "standard", draftInitialTime: 30, draftIncrementTime: 30 })
|
2026-03-20 21:36:39 -07:00
|
|
|
);
|
2026-03-24 17:00:32 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("owner pick — resets bank to exactly draftIncrementTime", async () => {
|
2026-03-20 21:36:39 -07:00
|
|
|
mockDb.returning
|
|
|
|
|
.mockResolvedValueOnce([mockDraftPick])
|
2026-03-24 17:00:32 -07:00
|
|
|
.mockResolvedValueOnce([{ id: "timer-1", teamId: TEAM_ID, timeRemaining: 30 }]);
|
2026-03-20 21:36:39 -07:00
|
|
|
|
|
|
|
|
await action({ request: makeRequest(), params: {}, context: ctx });
|
|
|
|
|
|
|
|
|
|
expect(mockSocketIO.emit).toHaveBeenCalledWith(
|
|
|
|
|
"timer-update",
|
|
|
|
|
expect.objectContaining({ teamId: TEAM_ID, timeRemaining: 30 })
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-24 17:00:32 -07:00
|
|
|
it("owner pick — fast picker does NOT accumulate time (bank never exceeds increment)", async () => {
|
|
|
|
|
// Team had 25s left (picked quickly); standard mode ignores prior balance
|
|
|
|
|
mockDb.query.draftTimers.findFirst.mockResolvedValue({ id: "timer-1", timeRemaining: 25 });
|
|
|
|
|
mockDb.returning
|
|
|
|
|
.mockResolvedValueOnce([mockDraftPick])
|
|
|
|
|
.mockResolvedValueOnce([{ id: "timer-1", teamId: TEAM_ID, timeRemaining: 30 }]);
|
|
|
|
|
|
|
|
|
|
await action({ request: makeRequest(), params: {}, context: ctx });
|
|
|
|
|
|
|
|
|
|
expect(mockSocketIO.emit).toHaveBeenCalledWith(
|
|
|
|
|
"timer-update",
|
|
|
|
|
expect.objectContaining({ teamId: TEAM_ID, timeRemaining: 30 })
|
2026-03-20 21:36:39 -07:00
|
|
|
);
|
2026-03-24 17:00:32 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("commissioner pick — resets bank to exactly draftIncrementTime", async () => {
|
Migrate authentication from Clerk to BetterAuth (#324)
* Migrate authentication from Clerk to BetterAuth (#322)
Replaces @clerk/react-router with self-hosted better-auth to eliminate
the external Clerk dependency and keep all user/session data in our own
PostgreSQL database.
**What changed**
- New: auth.server.ts (BetterAuth config w/ Drizzle adapter, bcrypt, Resend), auth-client.ts, api.auth.$.ts handler
- New: /login and /register pages with email+password and Google/Discord OAuth; open-redirect guard on redirectTo param
- New: UserMenu component replacing Clerk's UserButton
- Schema: sessions, accounts, verifications tables; emailVerified column; clerkId made nullable
- Migrations 0081 (BetterAuth tables) and 0082 (accounts extra columns for v1.6.9)
- All ~30 route files: getAuth → auth.api.getSession, isUserAdminByClerkId → isUserAdmin
- root.tsx: isAdmin read directly from session.user.isAdmin (no extra DB query)
- useDraftAuthRecovery: removed Clerk JWT refresh logic; replaced with cookie-session check
- models/user.ts: removed findUserByClerkId, findOrCreateUser, updateUserByClerkId (webhook pattern)
- Deleted: app/routes/api/webhooks/clerk.ts; uninstalled @clerk/react-router, @clerk/themes, svix
- scripts/migrate.mjs: extended with idempotent Clerk → BetterAuth data migration (FK conversion, email_verified, OAuth accounts)
- scripts/migrate-clerk-passwords.mjs: one-time script to import bcrypt hashes from Clerk CSV export
- BETTERAUTH_MIGRATION.md: dev and production runbooks
- All test mocks updated: vi.mock('~/lib/auth.server') instead of @clerk/react-router/server
- Test fixtures: added emailVerified field
**Follow-up (post-stable)**
- Rename actor_clerk_id column → actor_user_id in commissioner_audit_log
- Drop clerk_id column from users once migration confirmed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add .npmrc with legacy-peer-deps for better-auth/drizzle peer dep conflict
better-auth@1.6.9 declares peerOptional deps on drizzle-orm ^0.45.2 and
drizzle-kit >=0.31.4, but we run drizzle-orm ~0.36.3 / drizzle-kit ~0.28.1.
The adapter works correctly at runtime with our versions — the peer dep is
only for stricter type checking. This unblocks npm ci in CI without a risky
drizzle major-version upgrade.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 22:00:49 -07:00
|
|
|
const { auth } = await import("~/lib/auth.server");
|
|
|
|
|
vi.mocked(auth.api.getSession).mockResolvedValue({ user: { id: COMMISSIONER_ID } } as any);
|
2026-03-24 17:00:32 -07:00
|
|
|
mockDb.query.commissioners.findFirst.mockResolvedValue({ id: "c-1", userId: COMMISSIONER_ID });
|
|
|
|
|
mockDb.query.draftSlots.findMany.mockResolvedValue([
|
|
|
|
|
{ ...mockDraftSlots[0], team: { ...mockDraftSlots[0].team, ownerId: "someone-else" } },
|
|
|
|
|
mockDraftSlots[1],
|
|
|
|
|
]);
|
|
|
|
|
|
2026-03-20 21:36:39 -07:00
|
|
|
mockDb.returning
|
|
|
|
|
.mockResolvedValueOnce([mockDraftPick])
|
2026-03-24 17:00:32 -07:00
|
|
|
.mockResolvedValueOnce([{ id: "timer-1", teamId: TEAM_ID, timeRemaining: 30 }]);
|
2026-03-20 21:36:39 -07:00
|
|
|
|
|
|
|
|
await action({ request: makeRequest(), params: {}, context: ctx });
|
|
|
|
|
|
|
|
|
|
expect(mockSocketIO.emit).toHaveBeenCalledWith(
|
|
|
|
|
"timer-update",
|
|
|
|
|
expect.objectContaining({ teamId: TEAM_ID, timeRemaining: 30 })
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-24 17:00:32 -07:00
|
|
|
it("admin pick — resets bank to exactly draftIncrementTime", async () => {
|
Migrate authentication from Clerk to BetterAuth (#324)
* Migrate authentication from Clerk to BetterAuth (#322)
Replaces @clerk/react-router with self-hosted better-auth to eliminate
the external Clerk dependency and keep all user/session data in our own
PostgreSQL database.
**What changed**
- New: auth.server.ts (BetterAuth config w/ Drizzle adapter, bcrypt, Resend), auth-client.ts, api.auth.$.ts handler
- New: /login and /register pages with email+password and Google/Discord OAuth; open-redirect guard on redirectTo param
- New: UserMenu component replacing Clerk's UserButton
- Schema: sessions, accounts, verifications tables; emailVerified column; clerkId made nullable
- Migrations 0081 (BetterAuth tables) and 0082 (accounts extra columns for v1.6.9)
- All ~30 route files: getAuth → auth.api.getSession, isUserAdminByClerkId → isUserAdmin
- root.tsx: isAdmin read directly from session.user.isAdmin (no extra DB query)
- useDraftAuthRecovery: removed Clerk JWT refresh logic; replaced with cookie-session check
- models/user.ts: removed findUserByClerkId, findOrCreateUser, updateUserByClerkId (webhook pattern)
- Deleted: app/routes/api/webhooks/clerk.ts; uninstalled @clerk/react-router, @clerk/themes, svix
- scripts/migrate.mjs: extended with idempotent Clerk → BetterAuth data migration (FK conversion, email_verified, OAuth accounts)
- scripts/migrate-clerk-passwords.mjs: one-time script to import bcrypt hashes from Clerk CSV export
- BETTERAUTH_MIGRATION.md: dev and production runbooks
- All test mocks updated: vi.mock('~/lib/auth.server') instead of @clerk/react-router/server
- Test fixtures: added emailVerified field
**Follow-up (post-stable)**
- Rename actor_clerk_id column → actor_user_id in commissioner_audit_log
- Drop clerk_id column from users once migration confirmed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add .npmrc with legacy-peer-deps for better-auth/drizzle peer dep conflict
better-auth@1.6.9 declares peerOptional deps on drizzle-orm ^0.45.2 and
drizzle-kit >=0.31.4, but we run drizzle-orm ~0.36.3 / drizzle-kit ~0.28.1.
The adapter works correctly at runtime with our versions — the peer dep is
only for stricter type checking. This unblocks npm ci in CI without a risky
drizzle major-version upgrade.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 22:00:49 -07:00
|
|
|
const { auth } = await import("~/lib/auth.server");
|
|
|
|
|
vi.mocked(auth.api.getSession).mockResolvedValue({ user: { id: ADMIN_ID } } as any);
|
|
|
|
|
const { isUserAdmin } = await import("~/models/user");
|
|
|
|
|
vi.mocked(isUserAdmin).mockResolvedValue(true);
|
2026-03-24 17:00:32 -07:00
|
|
|
mockDb.query.draftSlots.findMany.mockResolvedValue([
|
|
|
|
|
{ ...mockDraftSlots[0], team: { ...mockDraftSlots[0].team, ownerId: "someone-else" } },
|
|
|
|
|
mockDraftSlots[1],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
mockDb.returning
|
|
|
|
|
.mockResolvedValueOnce([mockDraftPick])
|
|
|
|
|
.mockResolvedValueOnce([{ id: "timer-1", teamId: TEAM_ID, timeRemaining: 30 }]);
|
|
|
|
|
|
|
|
|
|
await action({ request: makeRequest(), params: {}, context: ctx });
|
|
|
|
|
|
|
|
|
|
expect(mockSocketIO.emit).toHaveBeenCalledWith(
|
|
|
|
|
"timer-update",
|
|
|
|
|
expect.objectContaining({ teamId: TEAM_ID, timeRemaining: 30 })
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("uses custom draftIncrementTime when configured", async () => {
|
2026-03-20 21:36:39 -07:00
|
|
|
mockDb.query.seasons.findFirst.mockResolvedValue(
|
2026-03-24 17:00:32 -07:00
|
|
|
makeSeason({ draftTimerMode: "standard", draftInitialTime: 90, draftIncrementTime: 90 })
|
2026-03-20 21:36:39 -07:00
|
|
|
);
|
|
|
|
|
mockDb.returning
|
|
|
|
|
.mockResolvedValueOnce([mockDraftPick])
|
2026-03-24 17:00:32 -07:00
|
|
|
.mockResolvedValueOnce([{ id: "timer-1", teamId: TEAM_ID, timeRemaining: 90 }]);
|
2026-03-20 21:36:39 -07:00
|
|
|
|
2026-03-24 17:00:32 -07:00
|
|
|
await action({ request: makeRequest(), params: {}, context: ctx });
|
2026-03-20 21:36:39 -07:00
|
|
|
|
2026-03-24 17:00:32 -07:00
|
|
|
expect(mockSocketIO.emit).toHaveBeenCalledWith(
|
|
|
|
|
"timer-update",
|
|
|
|
|
expect.objectContaining({ teamId: TEAM_ID, timeRemaining: 90 })
|
|
|
|
|
);
|
2026-03-20 21:36:39 -07:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|