mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-11 04:00:38 +00:00
303 lines
11 KiB
TypeScript
303 lines
11 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest'
|
|
import {
|
|
createSessionFormatChain,
|
|
defineSessionFormatMigration,
|
|
SessionFormatEventCollector,
|
|
SessionFormatUnsupportedMigrationError,
|
|
type SessionFormatEvent,
|
|
type SessionFormatEventRun,
|
|
type SessionFormatHeader,
|
|
type SessionFormatMigration,
|
|
type SessionFormatMigrationContext,
|
|
type SessionFormatMigrationStageInput,
|
|
} from '../src/index.ts'
|
|
|
|
const currentHeader: SessionFormatHeader = {
|
|
version: 1,
|
|
id: 'session-1',
|
|
createdAt: 1,
|
|
isSeeded: false,
|
|
delegationDepth: 0,
|
|
}
|
|
|
|
const event: SessionFormatEvent = {
|
|
type: 'turn/start', seq: 0, time: 2, data: { turn: 1 },
|
|
}
|
|
|
|
const discard: SessionFormatMigrationContext = {
|
|
emitEvent() {},
|
|
emitRun() {},
|
|
}
|
|
|
|
function throwUnknown(value: unknown): never {
|
|
throw value
|
|
}
|
|
|
|
function identityStage(inheritedEventCount: number | undefined) {
|
|
return {
|
|
...(inheritedEventCount === undefined ? {} : { headerInheritedEventCount: inheritedEventCount }),
|
|
transformEvent(
|
|
value: SessionFormatEvent,
|
|
context: SessionFormatMigrationContext,
|
|
) {
|
|
context.emitEvent(value)
|
|
},
|
|
transformRun(
|
|
run: SessionFormatEventRun,
|
|
context: SessionFormatMigrationContext,
|
|
) {
|
|
context.emitRun(run)
|
|
},
|
|
finish: () => inheritedEventCount ?? 0,
|
|
}
|
|
}
|
|
|
|
function migration(overrides: Partial<SessionFormatMigration> = {}): SessionFormatMigration {
|
|
return {
|
|
name: '@test/v0-to-v1',
|
|
fromVersion: 0,
|
|
toVersion: 1,
|
|
migrateHeader: header => ({ ...header, version: 1 }),
|
|
createStage: ({ sourceInheritedEventCount }) => identityStage(sourceInheritedEventCount),
|
|
validateTargetHeader: () => {},
|
|
...overrides,
|
|
}
|
|
}
|
|
|
|
function chain(edge: SessionFormatMigration = migration()) {
|
|
return createSessionFormatChain({
|
|
currentVersion: 1,
|
|
migrations: [edge],
|
|
restoreCurrentHeader: header => header,
|
|
})
|
|
}
|
|
|
|
function captureError(run: () => unknown): Error {
|
|
try {
|
|
run()
|
|
} catch (error: unknown) {
|
|
if (error instanceof Error) return error
|
|
throw new Error('expected an Error object', { cause: error })
|
|
}
|
|
throw new Error('expected callback to throw')
|
|
}
|
|
|
|
describe('Session format chain', () => {
|
|
it('bypasses adjacent stages for current input', () => {
|
|
const createStage = vi.fn(({ sourceInheritedEventCount }: SessionFormatMigrationStageInput) =>
|
|
identityStage(sourceInheritedEventCount))
|
|
const current = chain(migration({ createStage }))
|
|
const events = new SessionFormatEventCollector()
|
|
const stream = current.createStream(currentHeader, 0, events)
|
|
|
|
stream.emitEvent(event)
|
|
|
|
expect(stream.header).toEqual(currentHeader)
|
|
expect(stream.finish()).toBe(0)
|
|
expect(events.values).toEqual([event])
|
|
expect(createStage).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('composes one adjacent stage and its header conversion', () => {
|
|
const current = chain()
|
|
const sourceHeader = { ...currentHeader, version: 0 }
|
|
const events = new SessionFormatEventCollector()
|
|
const stream = current.createStream(sourceHeader, 0, events)
|
|
|
|
stream.emitEvent(event)
|
|
|
|
expect(stream.header.version).toBe(1)
|
|
expect(stream.finish()).toBe(0)
|
|
expect(events.values).toEqual([event])
|
|
expect(current.migrateHeader(sourceHeader).version).toBe(1)
|
|
})
|
|
|
|
it('rejects invalid declarations and incomplete chain construction', () => {
|
|
const base = migration()
|
|
expect(() => defineSessionFormatMigration({ ...base, name: '' })).toThrow(/name/)
|
|
expect(() => defineSessionFormatMigration({ ...base, toVersion: 2 })).toThrow(/adjacent/)
|
|
expect(() => createSessionFormatChain({
|
|
currentVersion: 1, migrations: [], restoreCurrentHeader: value => value,
|
|
})).toThrow(/missing/)
|
|
expect(() => createSessionFormatChain({
|
|
currentVersion: 1, migrations: [base, base], restoreCurrentHeader: value => value,
|
|
})).toThrow(/duplicated/)
|
|
expect(() => createSessionFormatChain({
|
|
currentVersion: 2,
|
|
migrations: [base, { ...base, name: base.name, fromVersion: 1, toVersion: 2 }],
|
|
restoreCurrentHeader: value => value,
|
|
})).toThrow(/name .* duplicated/)
|
|
expect(() => createSessionFormatChain({
|
|
currentVersion: 1,
|
|
migrations: [base, { ...base, name: '@test/v1-to-v2', fromVersion: 1, toVersion: 2 }],
|
|
restoreCurrentHeader: value => value,
|
|
})).toThrow(/does not lead/)
|
|
})
|
|
|
|
it('composes stages whose inherited cut is derived from the body', () => {
|
|
const first = migration({
|
|
createStage: () => ({
|
|
transformEvent: (value, context) => { context.emitEvent(value) },
|
|
transformRun: (value, context) => { context.emitRun(value) },
|
|
finish: () => 0,
|
|
}),
|
|
})
|
|
const second: SessionFormatMigration = {
|
|
...migration(),
|
|
name: '@test/v1-to-v2',
|
|
fromVersion: 1,
|
|
toVersion: 2,
|
|
migrateHeader: header => ({ ...header, version: 2 }),
|
|
}
|
|
const current = createSessionFormatChain({
|
|
currentVersion: 2,
|
|
migrations: [first, second],
|
|
restoreCurrentHeader: header => header,
|
|
})
|
|
|
|
expect(current.createStream(
|
|
{ ...currentHeader, version: 0 },
|
|
0,
|
|
discard,
|
|
).finish()).toBe(0)
|
|
|
|
const createSecondStage = vi.fn(({ sourceKind }: SessionFormatMigrationStageInput) => {
|
|
expect(sourceKind).toBe('transformed')
|
|
return identityStage(0)
|
|
})
|
|
const complete = createSessionFormatChain({
|
|
currentVersion: 2,
|
|
migrations: [migration(), { ...second, createStage: createSecondStage }],
|
|
restoreCurrentHeader: header => header,
|
|
})
|
|
const stream = complete.createStream(
|
|
{ ...currentHeader, version: 0 },
|
|
0,
|
|
discard,
|
|
)
|
|
expect(stream.finish()).toBe(0)
|
|
expect(createSecondStage).toHaveBeenCalledOnce()
|
|
})
|
|
|
|
it('rejects newer inputs and wrong header versions', () => {
|
|
const current = chain(migration({ migrateHeader: header => header }))
|
|
const source = { ...currentHeader, version: 0 }
|
|
|
|
expect(() => current.createStream({ ...currentHeader, version: 2 }, 0, discard)).toThrow(/newer/)
|
|
expect(() => current.createStream({ ...currentHeader, version: -1 }, 0, discard)).toThrow(/non-negative/)
|
|
expect(() => current.createStream(source, 0, discard)).toThrow(/header returned v0/)
|
|
expect(() => current.migrateHeader(source)).toThrow(/header returned v0/)
|
|
})
|
|
|
|
it('classifies stage failures as unsupported and preserves explicit refusals', () => {
|
|
const policyFailure = new Error('target relationship is invalid')
|
|
const source = { ...currentHeader, version: 0 }
|
|
const failed = chain(migration({ createStage: () => { throw policyFailure } }))
|
|
const refusal = captureError(() => failed.createStream(source, 0, discard))
|
|
expect(refusal).toBeInstanceOf(SessionFormatUnsupportedMigrationError)
|
|
expect(refusal.message).toContain('target relationship is invalid')
|
|
expect(refusal.cause).toBe(policyFailure)
|
|
|
|
const alreadyUnsupported = new SessionFormatUnsupportedMigrationError('explicit edge refusal')
|
|
const preserved = chain(migration({ createStage: () => { throw alreadyUnsupported } }))
|
|
expect(() => preserved.createStream(source, 0, discard)).toThrow(alreadyUnsupported)
|
|
|
|
const finishFailure = new Error('finish relationship is invalid')
|
|
const finishing = chain(migration({
|
|
createStage: () => ({
|
|
...identityStage(0),
|
|
finish: () => { throw finishFailure },
|
|
}),
|
|
})).createStream(source, 0, discard)
|
|
const finishRefusal = captureError(() => finishing.finish())
|
|
expect(finishRefusal).toBeInstanceOf(SessionFormatUnsupportedMigrationError)
|
|
expect(finishRefusal.cause).toBe(finishFailure)
|
|
|
|
const eventFailure = new Error('event relationship is invalid')
|
|
const eventFailing = chain(migration({
|
|
createStage: () => ({
|
|
...identityStage(0),
|
|
transformEvent: () => { throw eventFailure },
|
|
}),
|
|
})).createStream(source, 0, discard)
|
|
expect(() => { eventFailing.emitEvent(event) }).toThrow(/event relationship is invalid/)
|
|
|
|
const runFailure = new Error('run relationship is invalid')
|
|
const runFailing = chain(migration({
|
|
createStage: () => ({
|
|
...identityStage(0),
|
|
transformRun: () => { throw runFailure },
|
|
}),
|
|
})).createStream(source, 0, discard)
|
|
const runRefusal = captureError(() => {
|
|
runFailing.emitRun({
|
|
runType: 'test-run',
|
|
firstSeq: 0,
|
|
eventCount: 1,
|
|
*expand() { yield event },
|
|
})
|
|
})
|
|
expect(runRefusal).toBeInstanceOf(SessionFormatUnsupportedMigrationError)
|
|
expect(runRefusal.cause).toBe(runFailure)
|
|
|
|
const nonError = chain(migration({
|
|
createStage: () => throwUnknown('non-Error stage refusal'),
|
|
}))
|
|
expect(() => nonError.createStream(source, 0, discard))
|
|
.toThrow(/non-Error stage refusal/)
|
|
})
|
|
|
|
it('collects compact runs as expanded events', () => {
|
|
const collector = new SessionFormatEventCollector()
|
|
collector.emitRun({
|
|
runType: 'test-run',
|
|
firstSeq: 0,
|
|
eventCount: 1,
|
|
*expand() { yield event },
|
|
})
|
|
expect(collector.values).toEqual([event])
|
|
})
|
|
|
|
it('validates every adjacent target header and the final current header', () => {
|
|
const validateTargetHeader = vi.fn((header: SessionFormatHeader) => {
|
|
if (header['targetMarker'] !== true) throw new Error('target header lacks marker')
|
|
})
|
|
const restoreCurrentHeader = vi.fn((header: SessionFormatHeader) => {
|
|
if (typeof header.id !== 'string') throw new Error('current header lacks id')
|
|
return header
|
|
})
|
|
const current = createSessionFormatChain({
|
|
currentVersion: 1,
|
|
migrations: [migration({ validateTargetHeader })],
|
|
restoreCurrentHeader,
|
|
})
|
|
const source = { ...currentHeader, version: 0 }
|
|
|
|
const refusal = captureError(() => current.migrateHeader(source))
|
|
expect(refusal).toBeInstanceOf(SessionFormatUnsupportedMigrationError)
|
|
expect(refusal.message).toContain('target header lacks marker')
|
|
expect(validateTargetHeader).toHaveBeenCalledOnce()
|
|
|
|
const rejecting = createSessionFormatChain({
|
|
currentVersion: 1,
|
|
migrations: [migration({ migrateHeader: () => { throw new Error('historical header policy') } })],
|
|
restoreCurrentHeader,
|
|
})
|
|
expect(() => rejecting.migrateHeader(source)).toThrow(/historical header policy/)
|
|
|
|
const badCurrent = createSessionFormatChain({
|
|
currentVersion: 1,
|
|
migrations: [migration()],
|
|
restoreCurrentHeader: () => ({ version: 1 } as never),
|
|
})
|
|
expect(() => badCurrent.migrateHeader(currentHeader)).toThrow(/current Session header restoration id/)
|
|
|
|
const wrongCurrentVersion = createSessionFormatChain({
|
|
currentVersion: 1,
|
|
migrations: [migration()],
|
|
restoreCurrentHeader: header => ({ ...header, version: 0 }),
|
|
})
|
|
expect(() => wrongCurrentVersion.migrateHeader(currentHeader)).toThrow(/header restorer returned v0/)
|
|
})
|
|
})
|