mirror of
https://github.com/openclaw/openclaw.git
synced 2026-09-28 05:54:09 +08:00
test(gateway): bind restored requester fixture provenance (#155621)
Bind the restored-requester E2E seed to canonical physical requester and controller store identities, then verify the persisted SQLite round trip. Preserve the original four cases, deadlines and assertions. Capture bounded, redacted first-wait diagnostics and settle all Gateway/model cleanup owners. Test-fixture correction only; no production provenance guard, schema or dependency changes. Validation: exact-head CI 35719796768 passed. Reviewed production-plus-fixture composite de1bb285 passed all four real built-Gateway loopback E2E cases in native run 35722722579, with fulfilled fixture cleanup and verified host closure. This is not authenticated external-provider or release acceptance. Co-authored-by: RomneyDa <6581799+RomneyDa@users.noreply.github.com> Co-authored-by: steipete <58493+steipete@users.noreply.github.com> Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com> Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
This commit is contained in:
co-authored by
RomneyDa
steipete
vincentkoc
jalehman
parent
86fc1df067
commit
4383499752
@@ -8,9 +8,11 @@ import {
|
||||
saveSubagentRegistryToSqlite,
|
||||
} from "../src/agents/subagents/registry/subagent-registry.store.sqlite.js";
|
||||
import type { SubagentRunRecord } from "../src/agents/subagents/registry/subagent-registry.types.js";
|
||||
import { resolvePhysicalSessionStorePath } from "../src/config/sessions/session-store-path.js";
|
||||
import type { OpenClawConfig } from "../src/config/types.openclaw.js";
|
||||
import type { SessionsListResult } from "../src/gateway/session-utils.types.js";
|
||||
import { connectGatewayClient, disconnectGatewayClient } from "../src/gateway/test-helpers.e2e.js";
|
||||
import { redactSensitiveText } from "../src/logging/redact.js";
|
||||
import type { Deferred } from "../src/shared/deferred.js";
|
||||
import { closeOpenClawStateDatabaseForTest } from "../src/state/openclaw-state-db.js";
|
||||
import { writeOpenAiResponsesSse } from "./helpers/openai-responses-sse.js";
|
||||
@@ -45,8 +47,29 @@ afterEach(async () => {
|
||||
for (const server of modelServers) {
|
||||
server.releaseAll();
|
||||
}
|
||||
await Promise.allSettled(instances.splice(0).map((instance) => instance.cleanup()));
|
||||
await Promise.allSettled(modelServers.splice(0).map((server) => server.close()));
|
||||
const ownedInstances = instances.splice(0);
|
||||
const gatewayCleanup = await Promise.allSettled(
|
||||
ownedInstances.map((instance) => instance.cleanup()),
|
||||
);
|
||||
const modelCleanup = await Promise.allSettled(
|
||||
modelServers.splice(0).map((server) => server.close()),
|
||||
);
|
||||
console.info(
|
||||
"[restored-requester] cleanup",
|
||||
JSON.stringify({
|
||||
gateways: ownedInstances.map((instance, index) => ({
|
||||
name: instance.name,
|
||||
status: gatewayCleanup[index]?.status ?? "unavailable",
|
||||
})),
|
||||
modelServers: modelCleanup.map((result) => result.status),
|
||||
}),
|
||||
);
|
||||
const cleanupErrors = [...gatewayCleanup, ...modelCleanup].flatMap((result) =>
|
||||
result.status === "rejected" ? [result.reason] : [],
|
||||
);
|
||||
if (cleanupErrors.length > 0) {
|
||||
throw new AggregateError(cleanupErrors, "Restored requester fixture cleanup failed");
|
||||
}
|
||||
});
|
||||
|
||||
describe("Gateway restored requester settlement", () => {
|
||||
@@ -66,7 +89,7 @@ describe("Gateway restored requester settlement", () => {
|
||||
env: { OPENCLAW_SKIP_PROVIDERS: undefined, OPENCLAW_TEST_MINIMAL_GATEWAY: undefined },
|
||||
});
|
||||
instances.push(instance);
|
||||
await seedRestoredRequesters(instance, 1);
|
||||
await seedRestoredRequesters(instance, 1, cfg);
|
||||
await instance.startGateway();
|
||||
const client = await connectGatewayClient({
|
||||
url: instance.url,
|
||||
@@ -78,7 +101,11 @@ describe("Gateway restored requester settlement", () => {
|
||||
await client.request<SessionsListResult>("sessions.list", { agentId: "main" })
|
||||
).sessions.find((row) => row.key === sessionKey);
|
||||
try {
|
||||
await vi.waitFor(() => expect(modelServer.requestCount()).toBe(1), { timeout: 30_000 });
|
||||
await vi
|
||||
.waitFor(() => expect(modelServer.requestCount()).toBe(1), { timeout: 30_000 })
|
||||
.catch((error: unknown) => {
|
||||
throw gatewayDiagnosticError(instance, error);
|
||||
});
|
||||
const initial = await session();
|
||||
expect(initial).toMatchObject({ status: "running", hasActiveRun: true });
|
||||
// This elapsed wait is the regression itself: the real model socket must
|
||||
@@ -159,9 +186,10 @@ describe("Gateway restored requester settlement", () => {
|
||||
async () => {
|
||||
const modelServer = await startHeldModelServer();
|
||||
modelServers.push(modelServer);
|
||||
const cfg = createTestConfig(modelServer.url);
|
||||
const instance = await createOpenClawTestInstance({
|
||||
name: "gateway-restored-requester-settle",
|
||||
config: createTestConfig(modelServer.url),
|
||||
config: cfg,
|
||||
env: {
|
||||
OPENCLAW_SKIP_PROVIDERS: undefined,
|
||||
OPENCLAW_TEST_MINIMAL_GATEWAY: undefined,
|
||||
@@ -169,13 +197,17 @@ describe("Gateway restored requester settlement", () => {
|
||||
});
|
||||
instances.push(instance);
|
||||
|
||||
await seedRestoredRequesters(instance, 3);
|
||||
await seedRestoredRequesters(instance, 3, cfg);
|
||||
|
||||
await instance.startGateway();
|
||||
await vi.waitFor(
|
||||
() => expect(modelServer.countRequestsContaining(RESTORED_WAKE_MARKER)).toBe(2),
|
||||
{ interval: 20, timeout: 30_000 },
|
||||
);
|
||||
await vi
|
||||
.waitFor(() => expect(modelServer.countRequestsContaining(RESTORED_WAKE_MARKER)).toBe(2), {
|
||||
interval: 20,
|
||||
timeout: 30_000,
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
throw gatewayDiagnosticError(instance, error);
|
||||
});
|
||||
expect(modelServer.active(), instance.logs()).toBe(2);
|
||||
expect(modelServer.peakRestored(), instance.logs()).toBe(2);
|
||||
|
||||
@@ -198,16 +230,51 @@ describe("Gateway restored requester settlement", () => {
|
||||
);
|
||||
});
|
||||
|
||||
async function seedRestoredRequesters(instance: OpenClawTestInstance, count: number) {
|
||||
function gatewayDiagnosticError(instance: OpenClawTestInstance, cause: unknown): Error {
|
||||
const readiness = instance.readiness.map(
|
||||
({ outcome, elapsedMs, attempts, lastProbe, child }) => ({
|
||||
outcome,
|
||||
elapsedMs,
|
||||
attempts,
|
||||
lastProbe,
|
||||
child,
|
||||
}),
|
||||
);
|
||||
// Snapshot the existing bounded child buffers before normal fixture cleanup removes state.
|
||||
let diagnostic = redactSensitiveText(
|
||||
`${instance.name}\n${JSON.stringify({ readiness })}\n${instance.logs()}`,
|
||||
{ mode: "tools" },
|
||||
);
|
||||
for (const token of [instance.gatewayToken, instance.hookToken]) {
|
||||
if (token) {
|
||||
diagnostic = diagnostic.replaceAll(token, "[fixture token]");
|
||||
}
|
||||
}
|
||||
return new Error(diagnostic, { cause });
|
||||
}
|
||||
|
||||
async function seedRestoredRequesters(
|
||||
instance: OpenClawTestInstance,
|
||||
count: number,
|
||||
cfg: OpenClawConfig,
|
||||
) {
|
||||
instance.state.applyEnv();
|
||||
try {
|
||||
const endedAt = Date.now();
|
||||
const restoredRuns = Array.from({ length: count }, (_, index): SubagentRunRecord => {
|
||||
const runId = `run-gateway-restored-settle-${index}`;
|
||||
const requesterSessionKey = `agent:main:gateway-restored-requester-${index}`;
|
||||
const requesterStorePath = resolvePhysicalSessionStorePath(
|
||||
{ sessionKey: requesterSessionKey, agentId: "main", env: instance.env },
|
||||
cfg,
|
||||
);
|
||||
return {
|
||||
runId,
|
||||
childSessionKey: `agent:main:subagent:gateway-restored-settle-${index}`,
|
||||
requesterSessionKey: `agent:main:gateway-restored-requester-${index}`,
|
||||
requesterSessionKey,
|
||||
requesterAgentId: "main",
|
||||
requesterStorePath,
|
||||
controllerStorePath: requesterStorePath,
|
||||
requesterDisplayKey: `gateway-restored-requester-${index}`,
|
||||
task: "resume a durable requester wake through the Gateway CLI",
|
||||
cleanup: "keep",
|
||||
@@ -246,6 +313,19 @@ async function seedRestoredRequesters(instance: OpenClawTestInstance, count: num
|
||||
defaultSessionId: `gateway-restored-requester-${index}`,
|
||||
});
|
||||
}
|
||||
const persistedRuns = loadSubagentRegistryFromSqlite();
|
||||
for (const entry of restoredRuns) {
|
||||
// Direct SQLite fixtures must preserve the physical identity stamped by normal launch.
|
||||
const storePath = resolvePhysicalSessionStorePath(
|
||||
{ sessionKey: entry.requesterSessionKey, agentId: "main", env: instance.env },
|
||||
cfg,
|
||||
);
|
||||
expect(persistedRuns.get(entry.runId)).toMatchObject({
|
||||
requesterAgentId: "main",
|
||||
requesterStorePath: storePath,
|
||||
controllerStorePath: storePath,
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
// Keep this one state lease through the Gateway run and retained-result reads;
|
||||
// instance.cleanup owns restoration after every process has stopped.
|
||||
|
||||
Reference in New Issue
Block a user