session-fixture-layout.spec.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import { resolve } from 'node:path'
  2. import { describe, expect, it } from 'vitest'
  3. import { SessionSeq, type SessionEvent } from '@deepseek-ai/dsh-session'
  4. import { parseSessionLog } from '@deepseek-ai/dsh-llm-replay'
  5. import {
  6. canonicalSessionFixture,
  7. inspectSessionFixtureLayouts,
  8. isPhysicalSessionFixture,
  9. } from './session-fixture-layout.ts'
  10. const HEADER = ' {"type":"session","version":0,"id":"fixture","createdAt":1,"delegationDepth":0} '
  11. const root = resolve(import.meta.dirname, '..')
  12. function chunkRun(): SessionEvent[] {
  13. return Array.from({ length: 4 }, (_, index) => ({
  14. type: 'assistant/chunk',
  15. seq: SessionSeq(index + 2),
  16. time: 10 + index,
  17. data: {
  18. turn: 1,
  19. step: 1,
  20. chunk: { type: 'text-delta', index: 0, text: `part-${index}` },
  21. },
  22. }))
  23. }
  24. function fixtureEvents(): SessionEvent[] {
  25. return [
  26. { type: 'turn/start', seq: SessionSeq(0), time: 1, data: { turn: 1 } },
  27. { type: 'step/start', seq: SessionSeq(1), time: 2, data: { turn: 1, step: 1 } },
  28. ...chunkRun(),
  29. ]
  30. }
  31. function unpackedFixture(): string {
  32. return [HEADER, ...fixtureEvents().map(event => JSON.stringify(event)), ''].join('\n')
  33. }
  34. function decodedBody(content: string): SessionEvent[] {
  35. return parseSessionLog(content)
  36. }
  37. describe('canonicalSessionFixture', () => {
  38. it('preserves the header line and packs an unpacked event run losslessly', () => {
  39. const canonical = canonicalSessionFixture(unpackedFixture(), 'fixture.jsonl')
  40. expect(canonical).toBeDefined()
  41. expect(canonical?.split('\n')[0]).toBe(HEADER)
  42. const packed = canonical?.split('\n')
  43. .map(line => JSON.parse(line || '{}') as Record<string, unknown>)
  44. .find(record => record.type === 'text-chunks')
  45. expect(packed).toMatchObject({ type: 'text-chunks' })
  46. expect(packed).not.toHaveProperty('seq0')
  47. expect(packed).not.toHaveProperty('time0')
  48. expect(decodedBody(canonical ?? '').map(({ seq: _seq, time: _time, ...event }) => event))
  49. .toStrictEqual(fixtureEvents().map(({ seq: _seq, time: _time, ...event }) => event))
  50. })
  51. it('ignores JSONL whose first record is not a session header', () => {
  52. expect(canonicalSessionFixture('{"type":"session_event"}\n{"value":1}\n')).toBeUndefined()
  53. })
  54. it('is idempotent for an already packed fixture', () => {
  55. const packed = canonicalSessionFixture(unpackedFixture())
  56. expect(packed).toBeDefined()
  57. expect(canonicalSessionFixture(packed ?? '')).toBe(packed)
  58. })
  59. it('is idempotent for an already projected fixture', () => {
  60. const projected = [
  61. HEADER,
  62. '{"type":"turn/start","data":{"turn":1,"seq":99,"time":100}}',
  63. '',
  64. ].join('\n')
  65. expect(canonicalSessionFixture(projected)).toBe(projected)
  66. })
  67. it('fails loud on malformed records after a session header', () => {
  68. expect(() => canonicalSessionFixture(`${HEADER}\n{not-json}\n`, 'broken.jsonl'))
  69. .toThrow(/broken\.jsonl: session snapshot line 2 contains invalid JSON/)
  70. })
  71. it('labels malformed packed rows with the fixture path and line', () => {
  72. expect(() => canonicalSessionFixture(`${HEADER}\n{"type":"text-chunks"}\n`, 'broken.jsonl'))
  73. .toThrow(/broken\.jsonl: session snapshot line 2: released text-chunks row 0 lacks required member "data"/)
  74. })
  75. })
  76. describe('isPhysicalSessionFixture', () => {
  77. it('recognizes fixtures that preserve physical persistence encoding', () => {
  78. expect(isPhysicalSessionFixture(
  79. 'packages/experimental/webworker-runtime/tests/fixtures/vfs-example/home/sessions/--dsh-workspace--/main/session.jsonl',
  80. )).toBe(true)
  81. expect(isPhysicalSessionFixture(
  82. 'packages/experimental/webworker-runtime/tests/fixtures/vfs-example/home/sessions/--dsh-workspace--/main/session.v1.jsonl',
  83. )).toBe(true)
  84. expect(isPhysicalSessionFixture(
  85. 'scripts/snapshots/python-sdk-single-exe/advanced/session.1.jsonl',
  86. )).toBe(true)
  87. expect(isPhysicalSessionFixture(
  88. 'scripts/snapshots/python-sdk-single-exe/advanced/session.1.v1.jsonl',
  89. )).toBe(true)
  90. expect(isPhysicalSessionFixture(
  91. 'scripts/snapshots/python-sdk-single-exe/advanced/session.jsonl',
  92. )).toBe(true)
  93. expect(isPhysicalSessionFixture(
  94. 'scripts/snapshots/python-sdk-single-exe/restart/session.2.jsonl',
  95. )).toBe(true)
  96. expect(isPhysicalSessionFixture(
  97. 'packages/experimental/webworker-runtime/tests/fixtures/vfs-example/home/sessions/README.jsonl',
  98. )).toBe(false)
  99. expect(isPhysicalSessionFixture(
  100. 'scripts/snapshots/python-sdk-single-exe/advanced/requests.jsonl',
  101. )).toBe(false)
  102. expect(isPhysicalSessionFixture('apps/web/tests/snapshots/example/session.jsonl')).toBe(false)
  103. })
  104. })
  105. it('keeps every session-format JSONL fixture projected into canonical packed layout', () => {
  106. const nonCanonical = inspectSessionFixtureLayouts(root)
  107. .filter(fixture => fixture.source !== fixture.canonical)
  108. .map(fixture => fixture.path)
  109. expect(
  110. nonCanonical,
  111. 'Run `pnpm run migrate:packed-session-fixtures` and commit the mechanical fixture rewrite.',
  112. ).toEqual([])
  113. })