import { afterEach, beforeEach, expect, it, vi } from 'vitest'; import { newFixture, type Fixture } from '../helpers/fixture.js'; import { prepareNativeHost } from '../helpers/native-host.js'; let fixture: Fixture; beforeEach(async () => { vi.resetModules(); fixture = await newFixture([]); }); afterEach(async () => { await fixture.cleanup(); }); it('persists the manual requester while preserving source filtering and historical records', async () => { const { withRequestUsername } = await import('../../src/lib/server/auth/request-context.js'); const { beginDeployment, getDeployment, listDeployments } = await import('../../src/lib/server/provisioning/history.js'); const { closeDb } = await import('../../src/lib/server/db/index.js'); const legacy = beginDeployment('legacy', 'restart'); const [manual, automatic] = await withRequestUsername('alice', async () => [ beginDeployment('manual', 'restart'), beginDeployment('automatic', 'restart', 'watchdog:incident') ]); closeDb(); expect(getDeployment(manual)?.deployment).toMatchObject({ triggered_by: 'ui', requested_by: 'alice' }); expect(getDeployment(automatic)?.deployment).toMatchObject({ triggered_by: 'watchdog:incident', requested_by: null }); expect(getDeployment(legacy)?.deployment).toMatchObject({ triggered_by: 'ui', requested_by: null }); expect(listDeployments({ source: 'manual' }).map(row => row.id)).toEqual(expect.arrayContaining([manual, legacy])); expect(listDeployments({ source: 'manual' }).map(row => row.id)).not.toContain(automatic); }); it('isolates overlapping users and stops attributing deferred work after the request returns', async () => { const { withRequestUsername, currentRequestUsername } = await import('../../src/lib/server/auth/request-context.js'); let release!: () => void; const gate = new Promise(resolve => { release = resolve; }); let deferred!: Promise; const first = withRequestUsername('alice', async () => { deferred = gate.then(() => currentRequestUsername()); await Promise.resolve(); return currentRequestUsername(); }); const second = withRequestUsername('bob', async () => { await first; return currentRequestUsername(); }); expect(await Promise.all([first, second])).toEqual(['alice', 'bob']); expect(currentRequestUsername()).toBeNull(); release(); expect(await deferred).toBeNull(); }); it('records the operator adopting watchdog work but retains the first requester on repeated manual requests', async () => { const { db } = await import('../../src/lib/server/db/index.js'); const { beginDeployment, getDeployment } = await import('../../src/lib/server/provisioning/history.js'); const { requestLifecycle } = await import('../../src/lib/server/provisioning/requests.js'); const { withRequestUsername } = await import('../../src/lib/server/auth/request-context.js'); db().prepare("INSERT INTO servers(name) VALUES('server')").run(); await prepareNativeHost(['server']); const id = beginDeployment('server', 'restart', 'watchdog:incident'); db().prepare("UPDATE server_deployments SET state='waiting' WHERE id=?").run(id); db().prepare(`INSERT INTO watchdog_incidents(id,server,kind,state,summary,details,first_seen_at,last_seen_at) VALUES('incident','server','health','recovering','Unhealthy','{}',?,?)`).run(new Date().toISOString(), new Date().toISOString()); db().prepare("INSERT INTO provisioning_sources(operation_id,kind,source_id,server) VALUES(?,'watchdog','incident','server')").run(id); expect(await withRequestUsername('alice', async () => requestLifecycle('server', 'restart'))).toMatchObject({ ok: true, deploymentId: id }); expect(await withRequestUsername('bob', async () => requestLifecycle('server', 'restart'))).toMatchObject({ ok: true, deploymentId: id }); expect(getDeployment(id)?.deployment).toMatchObject({ requested_by: 'alice', triggered_by: 'ui' }); }); it('adds the requester column to existing databases without inventing historical usernames', async () => { const { db, closeDb } = await import('../../src/lib/server/db/index.js'); const { beginDeployment, getDeployment } = await import('../../src/lib/server/provisioning/history.js'); const id = beginDeployment('legacy', 'restart'); db().exec('ALTER TABLE server_deployments DROP COLUMN requested_by'); closeDb(); expect(getDeployment(id)?.deployment).toMatchObject({ requested_by: null, triggered_by: 'ui' }); });