Files
windmill/frontend/src/lib/components/sessions/sessionRunOwner.test.ts
T

76 lines
3.0 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest'
import {
clearRunPosition,
noteDriverAlive,
noteRemoteTurnEnded,
onDriverLost,
withSessionRunLock
} from './sessionRunOwner.svelte'
const SESSIONS = ['session-watching', 'session-catching-up', 'session-idle', 'session-unheard']
// `positions` is module state and a watching entry keeps the reaper interval
// armed, so leave nothing behind for the next suite.
afterEach(() => SESSIONS.forEach(clearRunPosition))
// The test env has no Web Locks API, which is the footing an origin served over
// plain HTTP runs on, so these exercise the fallback rather than the lock.
describe('withSessionRunLock with no lock to take', () => {
it('refuses while another tab is driving', async () => {
noteDriverAlive('session-watching', false)
const body = vi.fn(async () => 'ran')
expect(await withSessionRunLock('session-watching', body)).toBe('busy')
expect(body).not.toHaveBeenCalled()
})
// The window after the driver's turn ends and before this tab has re-read what
// it left behind. The run is over, so a check for "someone is driving" says
// go — but the transcript on screen is still paired with the history from
// before that turn, and sending would put that pair to the model.
it('refuses while still catching up on a finished turn', async () => {
onDriverLost(() => {})
noteDriverAlive('session-catching-up', false)
noteRemoteTurnEnded('session-catching-up')
const body = vi.fn(async () => 'ran')
expect(await withSessionRunLock('session-catching-up', body)).toBe('busy')
expect(body).not.toHaveBeenCalled()
})
it('takes a session no other tab holds', async () => {
const body = vi.fn(async () => 'ran')
expect(await withSessionRunLock('session-idle', body)).toBe('ran')
expect(body).toHaveBeenCalledTimes(1)
})
// A tab that opened between the driver's last status message and its turn-end
// never saw the run start, so it is idle — holding the conversation from
// before the turn, and idle is the position that permits driving on it.
it('refuses a turn-end it never saw start, rather than driving on stale history', async () => {
onDriverLost(() => {})
noteRemoteTurnEnded('session-unheard')
const body = vi.fn(async () => 'ran')
expect(await withSessionRunLock('session-unheard', body)).toBe('busy')
expect(body).not.toHaveBeenCalled()
})
})
// A fresh module instance is the only honest way to test this: the runtime's
// registration is process-wide and one-way, exactly as it is in a real tab.
describe('a turn ending in a tab with no session runtime', () => {
it('settles to idle rather than waiting for a re-read nobody can do', async () => {
vi.resetModules()
const owner = await import('./sessionRunOwner.svelte')
owner.noteDriverAlive('session-no-runtime', false)
owner.noteRemoteTurnEnded('session-no-runtime')
expect(owner.isCatchingUp('session-no-runtime')).toBe(false)
expect(owner.runHeldElsewhere('session-no-runtime')).toBe(false)
owner.clearRunPosition('session-no-runtime')
})
})