session-fixture-layout.spec.ts 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. import { describe, expect, it } from 'vitest'
  2. import { type SessionEvent } from '@deepseek-ai/dsh-session'
  3. import { parseSessionLog } from '@deepseek-ai/dsh-llm-replay'
  4. import { canonicalSessionFixture } from './session-fixture-layout.ts'
  5. const HEADER = ' {"type":"session","version":0,"id":"fixture","createdAt":1,"delegationDepth":0} '
  6. function chunkRun(): SessionEvent[] {
  7. return Array.from({ length: 4 }, (_, index) => ({
  8. type: 'assistant/chunk',
  9. seq: index,
  10. time: 10 + index,
  11. data: {
  12. turn: 1,
  13. step: 1,
  14. chunk: { type: 'text-delta', index: 0, text: `part-${index}` },
  15. },
  16. }))
  17. }
  18. function unpackedFixture(): string {
  19. return [HEADER, ...chunkRun().map(event => JSON.stringify(event)), ''].join('\n')
  20. }
  21. function decodedBody(content: string): SessionEvent[] {
  22. return parseSessionLog(content)
  23. }
  24. describe('canonicalSessionFixture', () => {
  25. it('preserves the header line and packs an unpacked event run losslessly', () => {
  26. const canonical = canonicalSessionFixture(unpackedFixture(), 'fixture.jsonl')
  27. expect(canonical).toBeDefined()
  28. expect(canonical?.split('\n')[0]).toBe(HEADER)
  29. const packed = JSON.parse(canonical?.split('\n')[1] ?? '{}') as Record<string, unknown>
  30. expect(packed).toMatchObject({ type: 'text-chunks' })
  31. expect(packed).not.toHaveProperty('seq0')
  32. expect(packed).not.toHaveProperty('time0')
  33. expect(decodedBody(canonical ?? '').map(({ seq: _seq, time: _time, ...event }) => event))
  34. .toStrictEqual(chunkRun().map(({ seq: _seq, time: _time, ...event }) => event))
  35. })
  36. it('ignores JSONL whose first record is not a session header', () => {
  37. expect(canonicalSessionFixture('{"type":"session_event"}\n{"value":1}\n')).toBeUndefined()
  38. })
  39. it('is idempotent for an already packed fixture', () => {
  40. const packed = canonicalSessionFixture(unpackedFixture())
  41. expect(packed).toBeDefined()
  42. expect(canonicalSessionFixture(packed ?? '')).toBe(packed)
  43. })
  44. it('is idempotent for an already projected fixture', () => {
  45. const projected = [
  46. HEADER,
  47. '{"type":"turn/start","data":{"turn":1,"seq":99,"time":100}}',
  48. '',
  49. ].join('\n')
  50. expect(canonicalSessionFixture(projected)).toBe(projected)
  51. })
  52. it('fails loud on malformed records after a session header', () => {
  53. expect(() => canonicalSessionFixture(`${HEADER}\n{not-json}\n`, 'broken.jsonl'))
  54. .toThrow(/broken\.jsonl: session snapshot line 2 contains invalid JSON/)
  55. })
  56. it('labels malformed packed rows with the fixture path and line', () => {
  57. expect(() => canonicalSessionFixture(`${HEADER}\n{"type":"text-chunks"}\n`, 'broken.jsonl'))
  58. .toThrow(/broken\.jsonl: session snapshot line 2: malformed text-chunks storage row/)
  59. })
  60. })