🌳
pt0/gamesapp/testsF/gamesappSuiteRunnersAI.mts
1// Gamesapp-specific test suite runners
2import * as _ from 'lodash-es'
3import { sql } from 'kysely'
17export const runUnitSuite: SuiteRunner = async ({ singleTestName }) => {
18 const {tests, test} = createTestCollector()
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}`}
27 seen.set(short, full)
28 }
29 return {passed: true, msg: `${allModels.length} models`}
30 })
31 return runTestsWithProgress({tests, suiteName: 'unit', singleTestName})
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
38 }))
39 const lowerName = singleTestName?.toLowerCase()
40 const filteredResults = lowerName ? testResults.filter(r => r.name.toLowerCase().includes(lowerName)) : testResults
41 printVerboseResults(filteredResults, {singleIdx: singleTestName ? 0 : undefined, suiteName: 'state'})
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 }) => {
47 const { results } = await replayRecentGames({ limit, verbose })
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}` : ''}`
50 }))
51 const lowerName = singleTestName?.toLowerCase()
52 const filteredResults = lowerName ? testResults.filter(r => r.name.toLowerCase().includes(lowerName)) : testResults
53 printVerboseResults(filteredResults, {singleIdx: singleTestName ? 0 : undefined, suiteName: 'db'})
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 () => {
59 const db = getGamesDb()
60 const testEmail = `test-room-owner@${testEmailDomain}`
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()
67 return inserted!.id
70const ensureTestRoomExists = async (roomLabel: string) => {
71 const db = getGamesDb()
72 const existing = await db.selectFrom('gamerooms').where('room_label', '=', roomLabel).select('id').executeTakeFirst()
73 if (existing) return
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)
84 return createLiveHarness({ baseUrl: baseUrl!, playerCount, gameRoom })
85 }
86 const onRound = ({ round, actionsThisRound }: { round: number; actionsThisRound: number }) => process.stdout.write(`\rRound ${round}: ${actionsThisRound} actions`)
87 const uiTests = [
88 { name: 'happyPath4Players', fn: async () => {
89 const result = await scenarios.happyPath4Players({ createHarness, onRound, isLive: true })
90 return { passed: result.passed, msg: result.message }
91 }},
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: '' }
108 }},
109 ]
110 return runTestsWithProgress({ tests: uiTests, suiteName: 'ui', singleTestName })
113export const runScalingSuite = mkScalingSuite(scalingIntegration)