1// Gamesapp-specific test suite runners 2import * as _ from 'lodash-es' 3import { sql } from 'kysely' 17export const runUnitSuite: SuiteRunner = async ({ singleTestName }) => { 19 test('shortModelNamesUnique', () => { 20 const galleryModels = galleryModes.flatMap((m: {models: string[]}) => m.models) 21 const allModels = [...new Set([...Object.values(telePromptModelsH), ...galleryModels])] as string[] 22 const shortNames = allModels.map(getShortModelName) 23 const seen = new Map<string, string>() 24 for (let i = 0; i < allModels.length; i++) { 25 const short = shortNames[i], full = allModels[i] 26 if (seen.has(short)) return {passed: false, msg: `Collision: ${short} <- ${seen.get(short)} & ${full}`} 29 return {passed: true, msg: `${allModels.length} models`} 34export const runStateSuite: SuiteRunner = async ({ singleTestName }) => { 35 const { results } = await runAllScenarios() as { results: Record<string, {passed: boolean, message: string}> } 36 const testResults = _.toPairs(results).map(([name, result], idx) => ({ 37 idx, name, passed: result.passed, msg: result.message 39 const lowerName = singleTestName?.toLowerCase() 40 const filteredResults = lowerName ? testResults.filter(r => r.name.toLowerCase().includes(lowerName)) : testResults 42 const passedCount = filteredResults.filter(r => r.passed).length 43 return { passed: filteredResults.every(r => r.passed), message: `${passedCount}/${filteredResults.length} scenarios passed`, testResults: filteredResults } 46export const runDbSuite: SuiteRunner = async ({ limit, verbose, singleTestName }) => { 48 const testResults = results.map((r, idx) => ({ 49 idx, name: r.id || `game-${idx}`, passed: r.ok, msg: `${r.actionCount || 0} actions${r.dtAgo ? ` (${r.dtAgo})` : ''}${r.message ? ` - ${r.message}` : ''}` 51 const lowerName = singleTestName?.toLowerCase() 52 const filteredResults = lowerName ? testResults.filter(r => r.name.toLowerCase().includes(lowerName)) : testResults 54 const passedCount = filteredResults.filter(r => r.passed).length 55 return { passed: filteredResults.every(r => r.passed), message: `${passedCount}/${filteredResults.length} games replayed`, testResults: filteredResults } 58const getOrCreateTestOwner = async () => { 61 const existing = await db.selectFrom('users').where('email', '=', testEmail).select('id').executeTakeFirst() 62 if (existing) return existing.id 63 const inserted = await db.insertInto('users').values({ 64 email: testEmail, updated_at: sql`now()`, 65 data: {} as never, faucet_ips: {} as never, // required by schema but not used for test owner 66 }).returning('id').executeTakeFirst() 70const ensureTestRoomExists = async (roomLabel: string) => { 72 const existing = await db.selectFrom('gamerooms').where('room_label', '=', roomLabel).select('id').executeTakeFirst() 74 const ownerUserId = await getOrCreateTestOwner() 75 await db.updateTable('gamerooms').set({is_gameactive: false}).where('owner_userid', '=', ownerUserId).where('is_gameactive', '=', true).execute() 76 await db.insertInto('gamerooms').values({room_label: roomLabel, owner_userid: ownerUserId, jdata: {}, updated_at: sql`now()`}).execute() 79export const runUiSuite: SuiteRunner = async ({ baseUrl, singleTestName }) => { 80 const gameRoomBase = `ui-test-${Date.now()}` 81 const createHarness = async ({ seed = 'test', playerCount = 4 }: { seed?: string; playerCount?: number } = {}) => { 82 const gameRoom = `${gameRoomBase}-${seed}` 83 await ensureTestRoomExists(gameRoom) 86 const onRound = ({ round, actionsThisRound }: { round: number; actionsThisRound: number }) => process.stdout.write(`\rRound ${round}: ${actionsThisRound} actions`) 88 { name: 'happyPath4Players', fn: async () => { 89 const result = await scenarios.happyPath4Players({ createHarness, onRound, isLive: true }) 90 return { passed: result.passed, msg: result.message } 92 { name: 'freshRoomShowsJoinPage', fn: async () => { 93 const { chromium } = await import('playwright') 94 const gameRoom = `${gameRoomBase}-fresh` 95 await ensureTestRoomExists(gameRoom) 96 const browser = await chromium.launch({ headless: true }) 97 const context = await browser.newContext() 98 const page = await context.newPage() 99 await context.addCookies([{ name: 'gameShardCook', value: gameRoom, domain: new URL(baseUrl!).hostname, path: '/' }]) 100 await page.goto(`${baseUrl}/${gameRoom}`, { waitUntil: 'load' }) 101 await page.waitForTimeout(3000) 102 const joinVisible = await page.locator('[data-testid="alias-input"]').isVisible().catch(() => false) 103 const playerStatuses = await page.locator('[data-testid="player-statuses"]').isVisible().catch(() => false) 104 await browser.close() 105 if (!joinVisible) return { passed: false, msg: 'join page (alias-input) not visible on fresh room' } 106 if (playerStatuses) return { passed: false, msg: 'player-statuses visible on fresh room (leaked players)' } 107 return { passed: true, msg: '' }