import { afterEach, beforeEach, expect, it, vi } from 'vitest'; import { newFixture, type Fixture } from '../helpers/fixture.js'; let fixture: Fixture; beforeEach(async () => { vi.resetModules(); fixture = await newFixture([]); }); afterEach(async () => { await fixture.cleanup(); }); it('bounds beta pages, searches across retained history, and seeks activity without duplicates', async () => { const { db } = await import('../../src/lib/server/db/index.js'); const { betaListPage, betaActivityPage } = await import('../../src/lib/server/fleet/beta-pages.js'); db().exec(`INSERT INTO scripts(id,name,target_rel_path) VALUES(1,'Plugin','plugin.dll'); INSERT INTO script_versions(id,script_id,version,sha256,size) VALUES(1,1,1,'hash',0)`); db().transaction(() => { for (let id = 1; id <= 125; id++) { db() .prepare( 'INSERT INTO betas(id,script_id,script_version_id,trigger_kind,state,notes) VALUES(?,1,1,?,?,?)' ) .run(id, 'ui', id <= 5 ? 'running' : 'promoted', `Test ${id}`); db() .prepare( 'INSERT INTO beta_scripts(beta_id,script_id,script_version_id) VALUES(?,1,1)' ) .run(id); } for (let i = 0; i < 150; i++) db() .prepare('INSERT INTO beta_events(beta_id,message) VALUES(1,?)') .run(`Activity ${i}`); })(); const page = betaListPage(new URLSearchParams()); expect(page.betas).toHaveLength(25); expect(page).toMatchObject({ total: 125, page: 1, totalPages: 5, counts: { active: 5, history: 120 } }); expect(betaListPage(new URLSearchParams('view=active')).betas.map((beta) => beta.id)).toEqual([ 5, 4, 3, 2, 1 ]); expect( betaListPage(new URLSearchParams('view=history&q=Test%20101')).betas.map((beta) => beta.id) ).toEqual([101]); expect(betaListPage(new URLSearchParams('q=Plugin&limit=100000')).betas).toHaveLength(100); const second = betaListPage(new URLSearchParams('page=2')); expect(second.betas.some((beta) => page.betas.some((other) => other.id === beta.id))).toBe( false ); const latest = betaActivityPage(1); expect(latest.events).toHaveLength(50); db().prepare("INSERT INTO beta_events(beta_id,message) VALUES(1,'New event')").run(); const previous = betaActivityPage( 1, new URLSearchParams({ before: String(latest.nextBefore) }) ); expect(previous.events).toHaveLength(50); expect(previous.events.at(-1)!.id).toBeLessThan(latest.events[0].id); const { GET } = await import('../../src/routes/api/betas/[id]/+server.js'); expect((await (await GET({ params: { id: '1' } })).json()).events).toHaveLength(50); }); it('coalesces beta progress notifications after synchronous database work', async () => { const { invalidateBeta, subscribe } = await import('../../src/lib/server/pipeline/bus.js'); const events: unknown[] = []; const stop = subscribe((event) => events.push(event)); try { invalidateBeta(1); invalidateBeta(1); invalidateBeta(2); expect(events).toEqual([]); await Promise.resolve(); expect(events).toEqual([ { type: 'beta-changed', betaId: 1 }, { type: 'beta-changed', betaId: 2 } ]); } finally { stop(); } }); it('filters by displayed status and searches servers and file paths before pagination', async () => { const { db } = await import('../../src/lib/server/db/index.js'); const { defaultPlan } = await import('../../src/lib/beta-plan.js'); const { betaListPage } = await import('../../src/lib/server/fleet/beta-pages.js'); const { betaStatus } = await import('../../src/lib/ui/betas.js'); const { GET } = await import('../../src/routes/api/betas/+server.js'); db().exec(`INSERT INTO scripts(id,name,target_rel_path) VALUES(1,'Plugin','special-file.dll'); INSERT INTO script_versions(id,script_id,version,sha256,size) VALUES(1,1,1,'hash',0); INSERT INTO servers(name) VALUES('t5_zombie_theater_3');`); const states = [ ['running', 'active', 'awaiting-empty'], ['ready-promote', 'passed', 'ready'], ['running', 'failed', 'failed'], ['running', 'paused', 'observing'], ['promoted', 'passed', 'ready'], ['rolled-back', 'paused', 'ready'], ['running', 'inconclusive', 'observing'] ]; for (let index = 0; index < 35; index++) { const [state, outcome, stage] = states[index % states.length]; const id = index + 1; db() .prepare( 'INSERT INTO betas(id,script_id,script_version_id,trigger_kind,state,notes) VALUES(?,1,1,?,?,?)' ) .run(id, 'ui', state, `Beta test ${id}`); db() .prepare('INSERT INTO beta_scripts(beta_id,script_id,script_version_id) VALUES(?,1,1)') .run(id); db() .prepare('INSERT INTO beta_servers(beta_id,server,stage,had_original) VALUES(?,?,?,0)') .run(id, 't5_zombie_theater_3', stage); db() .prepare('INSERT INTO beta_test_plans(beta_id,plan,outcome) VALUES(?,?,?)') .run(id, JSON.stringify(defaultPlan()), outcome); } const body = await (await GET({ url: new URL('http://localhost/api/betas?limit=100') })).json(); for (const status of ['ready', 'testing', 'attention', 'paused', 'promoted', 'rolled-back']) { const expected = body.betas.filter( (beta: Parameters[0]) => betaStatus(beta).key === status ); const result = betaListPage(new URLSearchParams({ status, limit: '100' })); expect(result.total).toBe(expected.length); expect(result.betas.map((beta) => beta.id)).toEqual( expected.map((beta: { id: number }) => beta.id) ); } expect(betaListPage(new URLSearchParams('q=Kino%20Der%20Toten')).total).toBe(35); expect(betaListPage(new URLSearchParams('q=special-file.dll')).total).toBe(35); expect(betaListPage(new URLSearchParams('q=%2335')).betas.map((beta) => beta.id)).toEqual([35]); const first = betaListPage(new URLSearchParams('view=active&status=attention&q=Kino&limit=3')); const second = betaListPage( new URLSearchParams('view=active&status=attention&q=Kino&limit=3&page=2') ); expect(first.total).toBe(10); expect(first.betas).toHaveLength(3); expect(second.betas.some((beta) => first.betas.some((other) => other.id === beta.id))).toBe( false ); });