fix(session-query): reject misplaced surface ops
This commit is contained in:
@@ -313,10 +313,13 @@ describe('Session', () => {
|
||||
expect(event.surfaceOp).toEqual({ op: 'replace', start: 0, end: 0 })
|
||||
})
|
||||
|
||||
it('adds seed context when surface validation throws a non-Error value', () => {
|
||||
it.each([
|
||||
['an Error', new Error('validator failed'), 'validator failed'],
|
||||
['a non-Error value', 'validator failed', 'invalid surface metadata'],
|
||||
] as const)('adds seed context when surface validation throws %s', (_name, failure, expected) => {
|
||||
const originalHasOwn = Object.hasOwn
|
||||
const hasOwn = vi.spyOn(Object, 'hasOwn').mockImplementation((object: object, property: PropertyKey): boolean => {
|
||||
if ((object as Record<string, unknown>)['op'] === 'replace') throw 'validator failed'
|
||||
if ((object as Record<string, unknown>)['op'] === 'replace') throw failure
|
||||
return originalHasOwn(object, property)
|
||||
})
|
||||
const seed = [{
|
||||
@@ -329,7 +332,7 @@ describe('Session', () => {
|
||||
|
||||
try {
|
||||
expect(() => new Session(SessionId('seed-non-error-metadata-failure'), seed))
|
||||
.toThrow('invalid seed event at index 0: invalid surface metadata')
|
||||
.toThrow(`invalid seed event at index 0: ${expected}`)
|
||||
} finally {
|
||||
hasOwn.mockRestore()
|
||||
}
|
||||
@@ -468,7 +471,7 @@ describe('Session', () => {
|
||||
'turn/start',
|
||||
{ turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } },
|
||||
{ surfaceOp: 'append' },
|
||||
)).toThrow(/not surface-eligible and cannot carry surface metadata/)
|
||||
)).toThrow(/not surface-eligible and cannot carry surfaceOp/)
|
||||
expect(() => new Session(SessionId('non-surface-metadata-seed'), [{
|
||||
type: 'turn/start',
|
||||
seq: 0,
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
foldSurface,
|
||||
isSurfaceEligibleType,
|
||||
isSurfaceEvent,
|
||||
validateSurfaceProvenance,
|
||||
validateSurfaceMetadata,
|
||||
} from '@deepseek-ai/dsh-session'
|
||||
import { CallId } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
@@ -31,11 +31,11 @@ function provenanceEvent(seq: number, sourceEventSeqs: unknown): SessionEvent {
|
||||
} as unknown as SessionEvent
|
||||
}
|
||||
|
||||
describe('validateSurfaceProvenance', () => {
|
||||
describe('validateSurfaceMetadata', () => {
|
||||
it('accepts absent or valid provenance and complete replacement coverage', () => {
|
||||
expect(validateSurfaceProvenance(provenanceEvent(0, undefined), new Set()))
|
||||
expect(validateSurfaceMetadata(provenanceEvent(0, undefined), new Set()))
|
||||
.toBeUndefined()
|
||||
expect(validateSurfaceProvenance(provenanceEvent(2, [0, 1]), new Set([0, 1]), [1]))
|
||||
expect(validateSurfaceMetadata(provenanceEvent(2, [0, 1]), new Set([0, 1]), [1]))
|
||||
.toBeUndefined()
|
||||
})
|
||||
|
||||
@@ -47,28 +47,33 @@ describe('validateSurfaceProvenance', () => {
|
||||
data: { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } },
|
||||
sourceEventSeqs: [0],
|
||||
} as unknown as SessionEvent
|
||||
expect(validateSurfaceProvenance(event, new Set([0])))
|
||||
.toMatch(/cannot carry sourceEventSeqs/)
|
||||
expect(validateSurfaceMetadata(event, new Set([0])))
|
||||
.toEqual({
|
||||
kind: 'provenance',
|
||||
message: 'turn/start cannot carry sourceEventSeqs (non-surface event)',
|
||||
})
|
||||
})
|
||||
|
||||
it.each([
|
||||
['a non-array', 1, 'invalid', new Set([0]), [], /must be an array/],
|
||||
['an empty array', 1, [], new Set([0]), [], /must not be empty/],
|
||||
['duplicates', 1, [0, 0], new Set([0]), [], /must not contain duplicates/],
|
||||
['a non-number', 1, ['0'], new Set([0]), [], /invalid seq 0/],
|
||||
['a fractional number', 1, [0.5], new Set([0]), [], /invalid seq 0\.5/],
|
||||
['a negative number', 1, [-1], new Set([0]), [], /invalid seq -1/],
|
||||
['a non-number', 1, ['0'], new Set([0]), [], /non-negative safe integers/],
|
||||
['a fractional number', 1, [0.5], new Set([0]), [], /non-negative safe integers/],
|
||||
['a negative number', 1, [-1], new Set([0]), [], /non-negative safe integers/],
|
||||
['a self reference', 1, [1], new Set([0]), [], /must reference earlier events/],
|
||||
['an unknown earlier seq', 2, [1], new Set([0]), [], /references unknown seq 1/],
|
||||
['incomplete replacement coverage', 2, [0], new Set([0, 1]), [0, 1], /missing 1/],
|
||||
] as const)(
|
||||
'returns the first violation for %s',
|
||||
(_name, seq, sources, knownSeqs, shadowedSeqs, expected) => {
|
||||
expect(validateSurfaceProvenance(
|
||||
const violation = validateSurfaceMetadata(
|
||||
provenanceEvent(seq, sources),
|
||||
knownSeqs,
|
||||
shadowedSeqs,
|
||||
)).toMatch(expected)
|
||||
)
|
||||
expect(violation?.kind).toBe('provenance')
|
||||
expect(violation?.message).toMatch(expected)
|
||||
},
|
||||
)
|
||||
})
|
||||
@@ -124,7 +129,20 @@ describe('SurfaceManager', () => {
|
||||
}
|
||||
|
||||
expect(() => foldSurface([malformed]))
|
||||
.toThrow(/surface event "user\/message" \(seq 0\) carries no surfaceOp marker/)
|
||||
.toThrow(/surface-eligible and requires a surfaceOp marker/)
|
||||
})
|
||||
|
||||
it('foldSurface rejects surfaceOp on a non-surface event', () => {
|
||||
const malformed = {
|
||||
type: 'turn/start',
|
||||
seq: 0,
|
||||
time: 1,
|
||||
data: { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } },
|
||||
surfaceOp: 'append',
|
||||
} as unknown as SessionEvent
|
||||
|
||||
expect(() => foldSurface([malformed]))
|
||||
.toThrow(/not surface-eligible and cannot carry surfaceOp/)
|
||||
})
|
||||
|
||||
it('rebuilds a linked list from surfaceOp: append markers', () => {
|
||||
|
||||
Reference in New Issue
Block a user