test(snapshot): centralize SDK session corpus

This commit is contained in:
Tianyi Cui
2026-08-24 21:36:31 +08:00
parent 84d6482a95
commit da1cb2c06e
21 changed files with 20 additions and 6 deletions
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/testing.md
testing.md: 9b0ed0a103b1ca9f149d289cabcc2b0ba9bc909f
testing.zh.md: 18e72deed19b7ecd981123e933f23cbbf7ba2c87
testing.md: 1d9f50e3d593e41ca0823e9b6a8182b573db5feb
testing.zh.md: dbe6bd4fedb779584998003587c71f5846d52c1c
+1 -1
View File
@@ -46,4 +46,4 @@ An e2e assertion re-runs the command or re-reads the file externally; a keyword
## When a snapshot test is required
Every non-trivial model-, protocol-, or human-visible change adds or updates a keyless scenario in the same PR through its owning recorded-session suite. Package tests, e2e assertions, mock/test-only compositions, and PR rationale do not replace the assembled transcript; extend the harness when needed. ACP automation scenarios live under `snapshots/acp/`, driven by the [`dsh-session-snapshot`](../packages/test-support/session-snapshot/README.md) ACP adapter; `examples/headless-agent` owns the internal canonical-event JSONL snapshots and replay fixtures. The `pwsh-tool-turn` ACP scenario boots real `pwsh` and skips where it is absent. Completed interactive-terminal journeys use JSONL-driven scenarios under `apps/cli/tests/snapshots/`; transient presentation uses the package-local semantic matrix, with a PTY case when input, Loader selection, or terminal teardown changes. Browser-rendered web GUI journeys use `apps/web/tests/snapshots/`. The two SDKs project the agent loop, session lifecycle, and `SessionEventMap` independently, so changing any of those updates both: `examples/python-sdk-agent/tests/snapshots/` owns the TypeScript client; `scripts/snapshots/python-sdk-single-exe/` owns the Python client, which only the required `python-runtime` CI job runs. New capability seams, lifecycle variants, or transcript surfaces name every coverage tier at plan time and verify the harness can express it before implementation.
Every non-trivial model-, protocol-, or human-visible change adds or updates a keyless scenario in the same PR through its owning recorded-session suite. Package tests, e2e assertions, mock/test-only compositions, and PR rationale do not replace the assembled transcript; extend the harness when needed. ACP automation scenarios live under `snapshots/acp/`, driven by the [`dsh-session-snapshot`](../packages/test-support/session-snapshot/README.md) ACP adapter; `examples/headless-agent` owns the internal canonical-event JSONL snapshots and replay fixtures. The `pwsh-tool-turn` ACP scenario boots real `pwsh` and skips where it is absent. Completed interactive-terminal journeys use JSONL-driven scenarios under `apps/cli/tests/snapshots/`; transient presentation uses the package-local semantic matrix, with a PTY case when input, Loader selection, or terminal teardown changes. Browser-rendered web GUI journeys use `apps/web/tests/snapshots/`. The two SDKs project the agent loop, session lifecycle, and `SessionEventMap` independently, so changing any of those updates both: `snapshots/sdk/` owns the TypeScript client; `scripts/snapshots/python-sdk-single-exe/` owns the Python client, which only the required `python-runtime` CI job runs. New capability seams, lifecycle variants, or transcript surfaces name every coverage tier at plan time and verify the harness can express it before implementation.
+1 -1
View File
@@ -46,4 +46,4 @@ e2e 断言应重新运行命令或从外部重新读取文件;对 agent 自身
## 何时需要快照测试
每项非平凡的模型可见、协议可见或人类可见变更,都必须在同一 PR 中,通过所属录制会话套件添加或更新无密钥场景。包测试、e2e 断言、mock 与仅测试组合、PR 理由都不能取代组装后的 transcript;必要时应扩展 harness。ACP 自动化场景位于 `snapshots/acp/`,由 [`dsh-session-snapshot`](../packages/test-support/session-snapshot/README.zh.md) ACP 适配器驱动;`examples/headless-agent` 拥有内部规范事件 JSONL 快照与回放 fixture。`pwsh-tool-turn` ACP 场景启动真实 `pwsh`,在无 `pwsh` 的主机上跳过。已完成的交互式终端旅程使用 `apps/cli/tests/snapshots/` 下由 JSONL 驱动的场景;瞬态呈现使用包内语义矩阵,输入、Loader 选择或终端清理发生变化时还要添加 PTY 用例。浏览器渲染的 Web GUI 旅程使用上述 Web 应用快照套件。两个 SDK 各自独立地投影 agent loop、会话生命周期与 `SessionEventMap`,因此改动其中任何一项都要同时更新两者:`examples/python-sdk-agent/tests/snapshots/` 拥有 TypeScript 客户端;`scripts/snapshots/python-sdk-single-exe/` 拥有 Python 客户端,且只有必需的 `python-runtime` CI 作业会运行它。新的能力 seam、生命周期变体或 transcript 呈现接口在计划阶段就要列出每个覆盖层级,并在实现前验证 harness 能够表达它们。
每项非平凡的模型可见、协议可见或人类可见变更,都必须在同一 PR 中,通过所属录制会话套件添加或更新无密钥场景。包测试、e2e 断言、mock 与仅测试组合、PR 理由都不能取代组装后的 transcript;必要时应扩展 harness。ACP 自动化场景位于 `snapshots/acp/`,由 [`dsh-session-snapshot`](../packages/test-support/session-snapshot/README.zh.md) ACP 适配器驱动;`examples/headless-agent` 拥有内部规范事件 JSONL 快照与回放 fixture。`pwsh-tool-turn` ACP 场景启动真实 `pwsh`,在无 `pwsh` 的主机上跳过。已完成的交互式终端旅程使用 `apps/cli/tests/snapshots/` 下由 JSONL 驱动的场景;瞬态呈现使用包内语义矩阵,输入、Loader 选择或终端清理发生变化时还要添加 PTY 用例。浏览器渲染的 Web GUI 旅程使用上述 Web 应用快照套件。两个 SDK 各自独立地投影 agent loop、会话生命周期与 `SessionEventMap`,因此改动其中任何一项都要同时更新两者:`snapshots/sdk/` 拥有 TypeScript 客户端;`scripts/snapshots/python-sdk-single-exe/` 拥有 Python 客户端,且只有必需的 `python-runtime` CI 作业会运行它。新的能力 seam、生命周期变体或 transcript 呈现接口在计划阶段就要列出每个覆盖层级,并在实现前验证 harness 能够表达它们。
@@ -19,6 +19,7 @@ import {
normalizeSessionLog,
normalizeSessionSnapshot,
normalizeStdout,
parseSnapshotManifest,
refreshFixtureReplacements,
scrubRequestHeaders,
scrubSessionSnapshot,
@@ -31,7 +32,7 @@ import {
import { DeepSeekHarness, type HarnessNotification, type RunResult } from '@deepseek-ai/dsh-sdk-client'
const testsDir = dirOf(import.meta.url)
const snapshotsDir = join(testsDir, 'snapshots')
const snapshotsDir = fileURLToPath(new URL('../../../snapshots/sdk/', import.meta.url))
const liveConfig = join(testsDir, '..', 'typescript-sdk.cordis.yml')
const replayConfig = join(testsDir, '..', 'typescript-sdk.cordis.snapshot.yml')
const minimalLiveConfig = join(testsDir, '..', 'typescript-sdk-minimal.cordis.yml')
@@ -65,7 +66,7 @@ function dirOf(url: string): string {
}
interface SdkScenario {
/** Scenario name; the snapshots/<name> fixture directory. */
/** Scenario name; the `snapshots/sdk/<name>` fixture directory. */
name: string
/** The user prompt for the single SDK turn. */
prompt: string
@@ -370,6 +371,11 @@ describe('TypeScript SDK snapshots over the jsonrpc runtime', () => {
for (const scenario of SCENARIOS) {
it(`replays ${scenario.name} through the SDK`, async () => {
const scenarioDir = join(snapshotsDir, scenario.name)
const manifest = parseSnapshotManifest(
await readFile(join(scenarioDir, 'snapshot.yml'), 'utf8'),
join(scenarioDir, 'snapshot.yml'),
)
expect(manifest).toEqual({ version: 1, profile: 'sdk' })
const notificationsExpectedPath = join(scenarioDir, 'notifications.expected.jsonl')
const resultExpectedPath = join(scenarioDir, 'result.expected.json')
+2
View File
@@ -0,0 +1,2 @@
version: 1
profile: sdk
@@ -0,0 +1,2 @@
version: 1
profile: sdk
@@ -0,0 +1,2 @@
version: 1
profile: sdk
+2
View File
@@ -0,0 +1,2 @@
version: 1
profile: sdk