mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-02 22:52:58 +00:00
test(server): require one turn's events to share an identical turn id
Matching only the ordinal let two events of one turn pass with different nonces. The session matcher binds the nonce from the first id it sees, so every later id with the same ordinal must be the exact same string.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import type { ChildProcessWithoutNullStreams } from "node:child_process";
|
||||
import { EventEmitter } from "node:events";
|
||||
import { type Dirent, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
@@ -100,6 +100,12 @@ import { createTestLogger } from "../../../test-utils/test-logger.js";
|
||||
import { asInternals as castInternals, createStub } from "../../test-utils/class-mocks.js";
|
||||
import { AgentManager } from "../agent-manager.js";
|
||||
import { buildProviderRegistry } from "../provider-registry.js";
|
||||
import { sessionTurnIds } from "./test-utils/turn-ids.js";
|
||||
|
||||
let codexTurnId = sessionTurnIds("codex");
|
||||
beforeEach(() => {
|
||||
codexTurnId = sessionTurnIds("codex");
|
||||
});
|
||||
|
||||
interface CollaborationModeRecord {
|
||||
name: string;
|
||||
@@ -4634,7 +4640,7 @@ describe("Codex app-server provider", () => {
|
||||
await expect(child).resolves.toMatchObject({
|
||||
type: "provider_subagent",
|
||||
provider: "codex",
|
||||
turnId: expect.stringMatching(/^codex-turn-[0-9a-f]+-0$/),
|
||||
turnId: codexTurnId(0),
|
||||
event: {
|
||||
type: "upsert",
|
||||
id: "legacy-only-child-thread",
|
||||
@@ -4644,7 +4650,7 @@ describe("Codex app-server provider", () => {
|
||||
await expect(spawn).resolves.toMatchObject({
|
||||
type: "timeline",
|
||||
provider: "codex",
|
||||
turnId: expect.stringMatching(/^codex-turn-[0-9a-f]+-0$/),
|
||||
turnId: codexTurnId(0),
|
||||
item: {
|
||||
type: "tool_call",
|
||||
callId: "spawn-legacy-only-child",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
@@ -25,6 +25,7 @@ import type {
|
||||
AssistantMessageTimelineItem,
|
||||
AgentTimelineItem,
|
||||
} from "../agent-sdk-types.js";
|
||||
import { sessionTurnIds } from "./test-utils/turn-ids.js";
|
||||
|
||||
// Deliberately an independent literal rather than the production constant these tests
|
||||
// guard: deriving the boundary from OPENCODE_SERVER_STARTUP_TIMEOUT_MS would keep the
|
||||
@@ -32,14 +33,10 @@ import type {
|
||||
const EXPECTED_STARTUP_BUDGET_MS = 30_000;
|
||||
const EXPECTED_EVENT_READINESS_BUDGET_MS = 45_000;
|
||||
|
||||
/** Matches the ordinal of an OpenCode turn id; the instance nonce in the middle is random. */
|
||||
function openCodeTurnId(ordinal: number) {
|
||||
return expect.stringMatching(new RegExp(`^opencode-turn-[0-9a-f]+-${ordinal}$`));
|
||||
}
|
||||
|
||||
function isOpenCodeTurn(turnId: string | undefined, ordinal: number): boolean {
|
||||
return new RegExp(`^opencode-turn-[0-9a-f]+-${ordinal}$`).test(turnId ?? "");
|
||||
}
|
||||
let openCodeTurnId = sessionTurnIds("opencode");
|
||||
beforeEach(() => {
|
||||
openCodeTurnId = sessionTurnIds("opencode");
|
||||
});
|
||||
|
||||
function tmpCwd(): string {
|
||||
const dir = mkdtempSync(path.join(os.tmpdir(), "opencode-agent-test-"));
|
||||
@@ -2896,7 +2893,8 @@ describe("OpenCode adapter startTurn error handling", () => {
|
||||
}),
|
||||
);
|
||||
const autonomousCompletion = events.find(
|
||||
(event) => event.type === "turn_completed" && isOpenCodeTurn(event.turnId, 1),
|
||||
(event) =>
|
||||
event.type === "turn_completed" && openCodeTurnId(1).asymmetricMatch(event.turnId),
|
||||
);
|
||||
expect(autonomousCompletion?.type).toBe("turn_completed");
|
||||
if (autonomousCompletion?.type === "turn_completed") {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/** A Vitest asymmetric matcher for one provider turn id. */
|
||||
export interface TurnIdMatcher {
|
||||
asymmetricMatch(actual: unknown): boolean;
|
||||
toString(): string;
|
||||
toAsymmetricMatcher(): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches the turn ids one provider session issues. Each session mixes a random nonce into its
|
||||
* ids, so the first id seen binds that nonce and every later match must be the exact id
|
||||
* `<owner>-turn-<nonce>-<ordinal>`. Two events of one turn therefore match only when their ids
|
||||
* are identical. Create one per test.
|
||||
*/
|
||||
export function sessionTurnIds(owner: string): (ordinal: number) => TurnIdMatcher {
|
||||
let nonce: string | null = null;
|
||||
const pattern = new RegExp(`^${owner}-turn-([0-9a-f]+)-(\\d+)$`);
|
||||
return (ordinal) => {
|
||||
const describe = () => `${owner}-turn-${nonce ?? "<nonce>"}-${ordinal}`;
|
||||
return {
|
||||
asymmetricMatch(actual) {
|
||||
const match = typeof actual === "string" ? pattern.exec(actual) : null;
|
||||
if (!match || Number(match[2]) !== ordinal) return false;
|
||||
nonce ??= match[1]!;
|
||||
return match[1] === nonce;
|
||||
},
|
||||
toString: describe,
|
||||
toAsymmetricMatcher: describe,
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user