openclaw/test/runtime-agent.codex-initialization.integration.test.ts
Peter Steinberger 34a7c7b35b
test(gateway,agents,plugins): remove low-value tests (batch d399) (#179650)
* test(gateway): deslop y0075 tests

Share repeated transcript, provider, provenance, and companion fixtures while preserving distinct authority and lifecycle cases. The completed-thread reset case is covered by the warm backing-reset context case; active-model reset cancellation remains covered separately.

CI-only validation: oxfmt, plain oxlint, and git diff --check passed. No tests or coverage run. Shard lines 11676 -> 11493; the 35% target was not safely achievable without dropping distinct behavior or changing fixture isolation.

* test(gateway): consolidate y0075 sibling fixtures

Merge recap freshness and incognito preflight siblings with their owner suites, keeping preflight state cold in a separate lifecycle. Share recap seed/RPC fixtures and remove duplicate Code Mode, catalog, visibility, label, and automation-registration proof covered by retained native or owner cases.

CI-only validation: formatting, plain oxlint, and diff checks passed; no test or coverage runs. Original shard files total 11179 lines versus 11676 initially. Including recap support growth, total is 11301 versus 11756, a 3.87 percent reduction; 35 percent remains unmet without removing distinct behavior.

* test(gateway): share y0075 cancellation and recap fixtures

Move immediate cold/warm cancellation cases into the service suite: preparation first awaits the observer, so these cases do not reach the native context reader. Preserve all eight cases, native hydration coverage, and four active-model cancellation cases. Share recap publication/patch assertions and remove redundant diagnostics and case counters.

CI-only static validation: oxfmt, plain oxlint, and diff checks passed. No tests, coverage, or builds run. Shard total 11676 -> 11047 lines; including recap support, 11756 -> 11169. The 35 percent target remains unmet without sacrificing distinct proof.

* test(update-cli): deslop y0076 tests

* test(update-cli): remove redundant y0076 owner replays

* test(auto-reply): deslop y0077 tests

* test(gateway): deslop y0079 tests

* test(gateway): remove y0079 owner replays

* test(state): deslop y0078 tests

* test(state): consolidate native borrow and read fixtures

* test(pr): deslop y0080 tests

* test(pr): remove redundant y0080 proof layers

* test(agents): deslop y0081 tests

* test(agents): deduplicate y0081 owner-level coverage

* test(plugins): deslop y0082 tests

* test(plugins): consolidate y0082 registry and loader proof

* test(plugins): consolidate y0082 lifecycle fixtures

* test(core): deslop y0083 tests

* test(core): deduplicate y0083 owner coverage

* test(core): share y0083 fixture and response setup

* test(gateway): deslop y0084 tests

* test(state): retain proofs whose consolidation conflicted

* test: retain distinct boundary proofs and repair replay fixtures

* test: drop CI-failing replay reductions

* test: retain suite inventory after planner contract failure

* test: repair fixture types and drop async listener rewrite

* test: retain synchronous managed auth HTTP fixture
2026-10-21 17:47:52 +00:00

454 lines
19 KiB
TypeScript

import { StatementSync } from "node:sqlite";
import { expectDefined } from "@openclaw/normalization-core";
import { isRecord } from "@openclaw/normalization-core/record-coerce";
import { createPluginRuntimeMock } from "openclaw/plugin-sdk/plugin-test-runtime";
import { afterEach, expect, it, vi } from "vitest";
import { createCodexSessionInitializationFixtureForTest } from "../extensions/codex/test-api.js";
import {
loadSessionEntry,
loadTranscriptEvents,
rollbackAgentHarnessSessionEntryLifecycle,
} from "../src/config/sessions/session-accessor.js";
import { writeSessionEntry } from "../src/config/sessions/session-accessor.sqlite-entry-store.js";
import { sessionRewindHandlers } from "../src/gateway/server-methods/sessions-rewind.js";
import { callGatewayHandler } from "../src/gateway/server-methods/skills.test-helpers.js";
import * as sqliteAdmission from "../src/infra/sqlite-worker-operation-admission.js";
import {
createPluginStateKeyedStoreV2,
createPluginStateSyncKeyedStore,
type OpenAsyncKeyedStoreOptions,
type OpenKeyedStoreOptions,
type PluginStateActionAuthority,
} from "../src/plugin-state/plugin-state-store.js";
import { createEmptyPluginRegistry } from "../src/plugins/registry-empty.js";
import { markPluginRegistryActive } from "../src/plugins/registry-lifecycle.js";
import { withPluginRuntimeRegistryScope } from "../src/plugins/runtime/gateway-request-scope.js";
import { createRuntimeAgent } from "../src/plugins/runtime/runtime-agent.js";
import { createPluginRecord } from "../src/plugins/status.test-helpers.js";
import type { SessionInitialization } from "../src/sessions/session-initialization.js";
import { sessionChanges } from "../src/sessions/session-row-changes.js";
import * as upstreamLinks from "../src/sessions/session-upstream-links.js";
import { readSessionUpstreamLinkInDatabase } from "../src/sessions/session-upstream-links.kernel.js";
import {
openOpenClawAgentDatabase,
runOpenClawAgentWriteTransaction,
} from "../src/state/openclaw-agent-db.js";
import { openOpenClawStateDatabase } from "../src/state/openclaw-state-db.js";
import { observeMainThreadReads } from "../src/test-utils/main-thread-sql-spies.test-support.js";
import { withOpenClawTestState } from "../src/test-utils/openclaw-test-state.js";
function writeLink(input: Parameters<typeof upstreamLinks.upsertSessionUpstreamLink>[0]) {
expect(upstreamLinks.upsertSessionUpstreamLink(input)).toBe(true);
}
function readLink(sessionKey: string, agentId = "main") {
return readSessionUpstreamLinkInDatabase(openOpenClawStateDatabase().db, sessionKey, agentId);
}
afterEach(() => vi.restoreAllMocks());
it.each([
...[
"callback",
"import",
"before binding",
"after binding",
"final readiness",
"readiness publication",
"lost response",
"successor binding",
"successor link",
"existing link",
"source successor",
"rollback commit",
"native cleanup",
].map((failure) => ({ flow: "fork", failure })),
...["callback", "before binding", "after binding", "final readiness", "rollback commit"].map(
(failure) => ({ flow: "adoption", failure }),
),
])(
"preserves exactly the committed owner after $flow $failure failure",
async ({ flow, failure }) => {
await withOpenClawTestState({ label: "codex-initialization-owner" }, async (state) => {
const agent = createRuntimeAgent();
const runtime = createPluginRuntimeMock({
agent,
state: {
openKeyedStoreV2: <T>(
options: OpenAsyncKeyedStoreOptions,
authority: PluginStateActionAuthority = { assertCurrent() {} },
) => createPluginStateKeyedStoreV2<T>("codex", { ...options, env: state.env }, authority),
openSyncKeyedStore: <T>(options: OpenKeyedStoreOptions) =>
createPluginStateSyncKeyedStore<T>("codex", { ...options, env: state.env }),
},
});
const fixture = await createCodexSessionInitializationFixtureForTest({
runtime,
workspaceDir: state.workspaceDir,
});
const { params, sourceThread, forkedThread, native, bindingStore, harness } = fixture;
const sourceBinding = bindingStore.read(fixture.sourceIdentity);
const sourceEntry = loadSessionEntry(params.source);
let expectedSourceEntry = sourceEntry;
const replaceSource = () => {
runOpenClawAgentWriteTransaction(
(database) =>
writeSessionEntry(database, params.source.sessionKey, {
...expectDefined(sourceEntry, "source"),
sessionId: "source-successor",
lifecycleRevision: "successor-generation",
}),
{ agentId: "main" },
);
expectedSourceEntry = loadSessionEntry({ ...params.source, readConsistency: "latest" });
};
const sourceHistory = await loadTranscriptEvents(params.source);
let linkOperation: "write" | "cleanup" | undefined;
let linkGrantReads = 0;
let rollbackCommitRefused = false;
let rejectReadinessCommit = false;
let readinessCommitRefused = false;
let readinessPublicationRefused = false;
let readyAtPublication: ReturnType<typeof loadSessionEntry>;
let stopReadinessPublication: (() => void) | undefined;
const reads = observeMainThreadReads();
const upsertLink = upstreamLinks.upsertSessionUpstreamLinkWithCurrentSource;
const deleteLink = upstreamLinks.deleteSessionUpstreamLinkAsync;
const trackLink =
<Args extends unknown[], Result>(
operation: "write" | "cleanup",
run: (...args: Args) => Promise<Result>,
) =>
async (...args: Args) => {
linkOperation = operation;
try {
return await run(...args);
} finally {
linkOperation = undefined;
}
};
vi.spyOn(upstreamLinks, "upsertSessionUpstreamLinkWithCurrentSource").mockImplementation(
trackLink("write", upsertLink),
);
vi.spyOn(upstreamLinks, "deleteSessionUpstreamLinkAsync").mockImplementation(
trackLink("cleanup", deleteLink),
);
const createAdmission = sqliteAdmission.createSqliteWorkerOperationAdmission;
vi.spyOn(sqliteAdmission, "createSqliteWorkerOperationAdmission").mockImplementation(
(admit, attachment) => {
const operation = linkOperation;
return createAdmission((request, grant) => {
const publication =
isRecord(request.facts) && isRecord(request.facts.publication)
? request.facts.publication.kind
: undefined;
if (
rejectReadinessCommit &&
request.stage === "commit" &&
publication === "session-entry-patch-committed"
) {
rejectReadinessCommit = false;
readinessCommitRefused = true;
throw new Error("injected readiness failure");
}
if (
failure === "rollback commit" &&
request.stage === "commit" &&
publication === "session-native-binding"
) {
rollbackCommitRefused = true;
throw new Error("injected rollback failure");
}
const offsets = reads.calls.map((call) => call.mock.contexts.length);
try {
admit(request, grant);
} finally {
if (operation && (request.stage === "transaction" || request.stage === "commit")) {
for (const [index, call] of reads.calls.entries()) {
linkGrantReads += call.mock.contexts
.slice(offsets[index])
.filter(
(statement) =>
statement instanceof StatementSync &&
statement.sourceSQL.includes("session_upstream_links"),
).length;
}
}
}
}, attachment);
},
);
const registry = createEmptyPluginRegistry();
registry.plugins.push(createPluginRecord({ id: "codex" }));
registry.agentHarnesses.push({ pluginId: "codex", source: "runtime", harness });
markPluginRegistryActive(registry);
const invokeFork = async () => {
writeLink({
sessionKey: params.source.sessionKey,
agentId: "main",
catalogId: params.upstream.catalogId,
hostId: params.upstream.hostId,
upstreamKind: params.upstream.kind,
threadId: sourceThread.id,
upstreamRef: params.upstream.ref,
marker: null,
});
const result = await callGatewayHandler(
sessionRewindHandlers,
"sessions.fork",
{ sessionKey: params.source.sessionKey, entryId: params.source.entryId },
{
context: {
getRuntimeConfig: () => ({}),
getSessionEventSubscriberConnIds: () => new Set(),
broadcastToConnIds: vi.fn(),
},
},
);
expect(result.ok).not.toBeNull();
const message = isRecord(result.error) ? result.error.message : undefined;
return {
status: result.ok ? "created" : "failed",
...(typeof message === "string" ? { message } : {}),
};
};
const deletion = vi.spyOn(harness, "withSessionDeletion");
const mutate = bindingStore.mutate.bind(bindingStore);
let childSessionId: string | undefined;
let retained: SessionInitialization | undefined;
const create = runtime.agent.session.createSessionEntry;
vi.spyOn(runtime.agent.session, "createSessionEntry").mockImplementation(async (input) => {
const created = await create({
...input,
afterCreate: async (context) => {
childSessionId = context.sessionId;
params.targetKey = context.key;
retained = context.initialization;
if (failure === "callback") {
throw new Error("injected callback failure");
}
if (failure === "existing link") {
writeLink({
sessionKey: context.key,
agentId: context.agentId,
catalogId: "codex",
hostId: "gateway:local",
upstreamKind: "codex-app-server",
threadId: "existing-successor",
upstreamRef: { threadId: "existing-successor" },
marker: null,
});
successorLink = readLink(context.key, context.agentId);
}
let patch: Awaited<ReturnType<NonNullable<typeof input.afterCreate>>>;
try {
patch = await input.afterCreate!(context);
} finally {
if (failure === "import") {
// The import fault must not alter schema admission during rollback.
openOpenClawAgentDatabase({ agentId: "main" }).db.exec(
"DROP TRIGGER reject_import",
);
}
}
if (!patch) {
throw new Error("Codex initializer did not return its final patch");
}
rejectReadinessCommit = failure === "final readiness";
return patch;
},
});
if (failure === "lost response") {
expect(created.entry.initializationPending).toBeUndefined();
throw new Error("injected response loss after readiness");
}
return created;
});
let successorBinding: Awaited<ReturnType<typeof bindingStore.read>>;
let successorLink: ReturnType<typeof readSessionUpstreamLinkInDatabase>;
if (failure === "native cleanup") {
native.archiveThread.mockRejectedValue(new Error("injected archive failure"));
native.control.retireConnection = vi.fn();
}
vi.spyOn(bindingStore, "mutate").mockImplementation(async (identity, mutation, guard) => {
if (mutation.kind !== "set") {
return await mutate(identity, mutation, guard);
}
if (identity.kind === "session") {
childSessionId = identity.sessionId;
}
if (failure === "before binding") {
throw new Error("injected binding write failure");
}
const attached = await mutate(identity, mutation, guard);
expect(attached).toBe(true);
if (failure === "source successor") {
replaceSource();
}
if (failure === "successor binding") {
await mutate(identity, {
kind: "patch",
threadId: forkedThread.id,
patch: { model: "successor-model" },
});
successorBinding = bindingStore.read(identity);
}
if (failure === "successor link") {
writeLink({
...expectDefined(readLink(params.targetKey), "created link"),
marker: { turnId: "successor-turn", userMessageCount: 2 },
threadId: "successor-thread",
});
successorLink = readLink(params.targetKey);
}
if (
[
"after binding",
"successor binding",
"successor link",
"rollback commit",
"native cleanup",
].includes(failure)
) {
throw new Error("injected post-write failure");
}
if (failure === "readiness publication") {
stopReadinessPublication = sessionChanges.subscribe((change) => {
if (
readinessPublicationRefused ||
!("sessionKey" in change) ||
change.sessionKey !== params.targetKey
) {
return;
}
const ready = loadSessionEntry({
agentId: "main",
sessionKey: params.targetKey,
readConsistency: "latest",
});
if (
!ready ||
ready.sessionId !== childSessionId ||
ready.initializationPending === true
) {
return;
}
readyAtPublication = ready;
readinessPublicationRefused = true;
throw new Error("injected readiness publication failure");
});
}
return attached;
});
if (failure === "import") {
// Allow the child header so the callback captures identity before imported messages fail.
openOpenClawAgentDatabase({ agentId: "main" }).db.exec(
"CREATE TRIGGER reject_import BEFORE INSERT ON transcript_events WHEN NEW.navigation_type = 'message' BEGIN SELECT RAISE(ABORT, 'injected import failure'); END",
);
}
const result = await withPluginRuntimeRegistryScope(
registry,
flow === "fork" ? invokeFork : fixture.adopt,
).finally(() => stopReadinessPublication?.());
if (failure === "rollback commit") {
expect(rollbackCommitRefused).toBe(true);
}
if (failure === "import") {
expect(result.message).toContain("injected import failure");
}
if (failure === "final readiness") {
expect(readinessCommitRefused).toBe(true);
expect(result.message).toContain("injected readiness failure");
}
if (failure === "readiness publication") {
expect(readinessPublicationRefused).toBe(true);
expect(readyAtPublication?.sessionId).toBe(childSessionId);
expect(readyAtPublication?.initializationPending).toBeUndefined();
}
expect(linkGrantReads).toBe(0);
expect(result).toMatchObject({
status: failure === "readiness publication" ? "created" : "failed",
});
const identity = {
kind: "session" as const,
agentId: "main",
sessionKey: params.targetKey,
sessionId: expectDefined(childSessionId, "created child identity"),
};
const child = loadSessionEntry(identity);
const binding = bindingStore.read(identity);
const link = readLink(params.targetKey);
if (failure === "lost response" || failure === "readiness publication") {
expect(child?.initializationPending).toBeUndefined();
expect(child?.sessionId).toBe(childSessionId);
expect(binding?.threadId).toBe(forkedThread.id);
expect(link?.threadId).toBe(forkedThread.id);
expect(native.archiveThread).not.toHaveBeenCalled();
expect(deletion).not.toHaveBeenCalled();
} else if (["successor binding", "rollback commit"].includes(failure)) {
expect(child?.initializationPending).toBe(true);
expect(binding).toEqual(
successorBinding ??
expect.objectContaining({
threadId: flow === "fork" ? forkedThread.id : sourceThread.id,
}),
);
expect(link?.threadId).toBe(flow === "fork" ? forkedThread.id : undefined);
expect(native.archiveThread).not.toHaveBeenCalled();
expect(result).toMatchObject({
message: expect.stringContaining("guarded rollback did not complete"),
});
} else {
expect(deletion).toHaveBeenCalledOnce();
expect(child).toBeUndefined();
expect(binding).toBeUndefined();
expect(link).toEqual(successorLink);
expect(await loadTranscriptEvents(identity)).toEqual([]);
if (failure === "successor link" || flow === "adoption") {
expect(native.archiveThread).not.toHaveBeenCalled();
} else {
expect(native.archiveThread).toHaveBeenCalledExactlyOnceWith(
forkedThread.id,
failure === "callback"
? undefined
: expectDefined(retained, "initializer handle").assertRollbackCurrent,
);
}
if (failure === "native cleanup") {
expect(native.control.retireConnection).toHaveBeenCalledOnce();
expect(result).toMatchObject({
message: expect.stringContaining("guarded rollback did not complete"),
});
}
}
for (const method of ["assertCurrent", "assertRollbackCurrent"] as const) {
expect(() => expectDefined(retained, "initializer handle")[method]()).toThrow("closed");
}
if (failure === "lost response") {
await expect(
withPluginRuntimeRegistryScope(registry, () =>
rollbackAgentHarnessSessionEntryLifecycle({
agentId: "main",
storePath: params.source.storePath,
target: { canonicalKey: params.targetKey, storeKeys: [params.targetKey] },
expectedEntry: expectDefined(child, "ready child"),
expectedSessionId: identity.sessionId,
expectedUpdatedAt: child!.updatedAt,
archiveTranscript: true,
}),
),
).rejects.toThrow("owned by supervision");
expect(loadSessionEntry(identity)).toEqual(child);
expect(bindingStore.read(identity)).toEqual(binding);
}
expect(bindingStore.read(fixture.sourceIdentity)).toEqual(sourceBinding);
expect(loadSessionEntry(params.source)).toEqual(expectedSourceEntry);
expect(await loadTranscriptEvents(params.source)).toEqual(sourceHistory);
expect(native.archiveThread.mock.calls).not.toEqual(
expect.arrayContaining([expect.arrayContaining([sourceThread.id])]),
);
});
},
);