Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion app/packages/web/e2e/fixtures/game-data.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import type { GameStatus, CostEstimates, EnvInfo, WatchdogConfig, ActualCosts } from '@/api.js';
import type {
GameStatus,
CostEstimates,
EnvInfo,
WatchdogConfig,
ActualCosts,
DiscordConfigRedacted,
} from '@/api.js';

/** Stub response for `GET /api/env`. */
export const ENV_DATA: EnvInfo = {
Expand Down Expand Up @@ -108,3 +115,42 @@ export function makeActualCosts(days: number): ActualCosts {
const total = daily.reduce((sum, d) => sum + d.cost, 0);
return { daily, total: Math.round(total * 100) / 100, currency: 'USD', days };
}

/** A valid Discord snowflake (17–20 digits) for use in test inputs. */
export const VALID_GUILD_ID = '123456789012345678';
/** A second valid snowflake — useful for multi-guild specs. */
export const VALID_GUILD_ID_2 = '987654321098765432';
/** A valid user-shaped snowflake for admin/permission specs. */
export const VALID_USER_ID = '111122223333444455';

/**
* First-run Discord config — no guilds, no admins, no secrets configured.
* Triggers the `/discord` setup-wizard render path.
*/
export const FIRST_RUN_DISCORD_CONFIG: DiscordConfigRedacted = {
clientId: '',
allowedGuilds: [],
admins: { userIds: [], roleIds: [] },
gamePermissions: {},
baseAllowedGuilds: [],
baseAdmins: { userIds: [], roleIds: [] },
botTokenSet: false,
publicKeySet: false,
interactionsEndpointUrl: null,
};

/**
* Fully-configured Discord config — bot token + public key set, one allowlisted
* guild, one admin user. Used to exercise the post-setup tabs.
*/
export const CONFIGURED_DISCORD_CONFIG: DiscordConfigRedacted = {
clientId: '111111111111111111',
allowedGuilds: [VALID_GUILD_ID],
admins: { userIds: [VALID_USER_ID], roleIds: [] },
gamePermissions: {},
baseAllowedGuilds: [],
baseAdmins: { userIds: [], roleIds: [] },
botTokenSet: true,
publicKeySet: true,
interactionsEndpointUrl: 'https://abc123.lambda-url.us-east-1.on.aws/',
};
68 changes: 65 additions & 3 deletions app/packages/web/e2e/fixtures/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
import { test as base, type Page } from '@playwright/test';
import type { GameStatus, CostEstimates, EnvInfo, ActionResult, WatchdogConfig, ActualCosts } from '@/api.js';
import { ENV_DATA, STOPPED_GAME, COST_DATA, WATCHDOG_CONFIG, makeActualCosts } from './game-data.js';
import type {
GameStatus,
CostEstimates,
EnvInfo,
ActionResult,
WatchdogConfig,
ActualCosts,
DiscordConfigRedacted,
} from '@/api.js';
import {
ENV_DATA,
STOPPED_GAME,
COST_DATA,
WATCHDOG_CONFIG,
CONFIGURED_DISCORD_CONFIG,
makeActualCosts,
} from './game-data.js';
import { AppLayout, AuthGatePage, DashboardPage, CostsPage } from '../pages/index.js';

export type { GameStatus, CostEstimates, EnvInfo, WatchdogConfig, ActualCosts };
export type {
GameStatus,
CostEstimates,
EnvInfo,
WatchdogConfig,
ActualCosts,
DiscordConfigRedacted,
};
export {
ENV_DATA,
STOPPED_GAME,
Expand All @@ -14,6 +36,11 @@ export {
WATCHDOG_CONFIG,
ACTUAL_COSTS,
makeActualCosts,
FIRST_RUN_DISCORD_CONFIG,
CONFIGURED_DISCORD_CONFIG,
VALID_GUILD_ID,
VALID_GUILD_ID_2,
VALID_USER_ID,
} from './game-data.js';
export { AppLayout, AuthGatePage, DashboardPage, CostsPage } from '../pages/index.js';

Expand All @@ -36,6 +63,13 @@ export interface StubOptions {
config?: WatchdogConfig;
/** Override for `POST /api/start/:game` response. */
startResult?: ActionResult;
/**
* Discord config returned by `GET /api/discord/config`. Defaults to
* `CONFIGURED_DISCORD_CONFIG` so non-Discord specs hitting `/discord` (e.g.
* sidebar nav) don't trip the catch-all 404 handler. Pass
* `FIRST_RUN_DISCORD_CONFIG` to exercise the setup wizard.
*/
discord?: DiscordConfigRedacted;
}

/**
Expand All @@ -54,6 +88,8 @@ export async function stubApis(page: Page, opts: StubOptions = {}): Promise<void
const env = opts.env ?? ENV_DATA;
const config = opts.config ?? WATCHDOG_CONFIG;
const startResult: ActionResult = opts.startResult ?? { success: true, message: 'Started' };
const discord = opts.discord ?? CONFIGURED_DISCORD_CONFIG;
const games = statuses.map((s) => s.game);
const actualCostsFn: (days: number) => ActualCosts =
typeof opts.actualCosts === 'function'
? opts.actualCosts
Expand All @@ -75,6 +111,8 @@ export async function stubApis(page: Page, opts: StubOptions = {}): Promise<void
return route.fulfill({ json: s });
});

await page.route('**/api/games', (route) => route.fulfill({ json: { games } }));

await page.route('**/api/costs/estimate', (route) => route.fulfill({ json: costs }));

// Trailing `*` matches the `?days=N` query string — Playwright globs are
Expand All @@ -97,6 +135,30 @@ export async function stubApis(page: Page, opts: StubOptions = {}): Promise<void
await page.route('**/api/stop/*', (route) =>
route.fulfill({ json: { success: true, message: 'Stopped' } as ActionResult })
);

// Discord — read endpoint plus permissive write endpoints. Specs that need
// to assert request bodies should override these with their own page.route().
await page.route('**/api/discord/config', (route) => {
if (route.request().method() === 'PUT') {
return route.fulfill({ json: { success: true, config: discord } });
}
return route.fulfill({ json: discord });
});
await page.route('**/api/discord/guilds', (route) =>
route.fulfill({ json: { success: true, guilds: discord.allowedGuilds } }),
);
await page.route('**/api/discord/guilds/*', (route) =>
route.fulfill({ json: { success: true, guilds: discord.allowedGuilds } }),
);
await page.route('**/api/discord/guilds/*/register-commands', (route) =>
route.fulfill({ json: { success: true, message: 'Registered' } }),
);
await page.route('**/api/discord/admins', (route) =>
route.fulfill({ json: { success: true, admins: discord.admins } }),
);
await page.route('**/api/discord/permissions/*', (route) =>
route.fulfill({ json: { success: true, permissions: discord.gamePermissions } }),
);
}

type E2EFixtures = {
Expand Down
Loading
Loading