mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
test: stabilize release docker e2e harnesses
This commit is contained in:
@@ -108,7 +108,16 @@ function pluginRequiresConfig(pluginDir) {
|
||||
}
|
||||
const manifest = readJson(manifestPath);
|
||||
const required = manifest.configSchema?.required;
|
||||
return Array.isArray(required) && required.some((value) => typeof value === "string");
|
||||
if (Array.isArray(required) && required.some((value) => typeof value === "string")) {
|
||||
return true;
|
||||
}
|
||||
const channelEnvVars =
|
||||
manifest.channelEnvVars && typeof manifest.channelEnvVars === "object"
|
||||
? Object.values(manifest.channelEnvVars)
|
||||
: [];
|
||||
return channelEnvVars.some(
|
||||
(envVars) => Array.isArray(envVars) && envVars.some((value) => typeof value === "string"),
|
||||
);
|
||||
}
|
||||
|
||||
async function loadPackagedBundledEntries() {
|
||||
|
||||
@@ -352,32 +352,43 @@ export async function connectMcpClient(params: {
|
||||
export async function maybeApprovePendingBridgePairing(
|
||||
gateway: GatewayRpcClient,
|
||||
): Promise<boolean> {
|
||||
let pairingState:
|
||||
| {
|
||||
for (let attempt = 0; attempt < 2; attempt += 1) {
|
||||
let pairingState:
|
||||
| {
|
||||
pending?: Array<{ requestId?: string; role?: string }>;
|
||||
}
|
||||
| undefined;
|
||||
try {
|
||||
pairingState = await gateway.request<{
|
||||
pending?: Array<{ requestId?: string; role?: string }>;
|
||||
}>("device.pair.list", {});
|
||||
} catch (error) {
|
||||
const message = formatErrorMessage(error);
|
||||
if (
|
||||
message.includes("missing scope: operator.pairing") ||
|
||||
message.includes("device.pair.list")
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
| undefined;
|
||||
try {
|
||||
pairingState = await gateway.request<{
|
||||
pending?: Array<{ requestId?: string; role?: string }>;
|
||||
}>("device.pair.list", {});
|
||||
} catch (error) {
|
||||
const message = formatErrorMessage(error);
|
||||
if (
|
||||
message.includes("missing scope: operator.pairing") ||
|
||||
message.includes("device.pair.list")
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
if (!pairingState) {
|
||||
return false;
|
||||
}
|
||||
throw error;
|
||||
const pendingRequest = pairingState.pending?.find((entry) => entry.role === "operator");
|
||||
if (!pendingRequest?.requestId) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
await gateway.request("device.pair.approve", { requestId: pendingRequest.requestId });
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (!formatErrorMessage(error).includes("unknown requestId")) {
|
||||
throw error;
|
||||
}
|
||||
// The gateway may auto-approve the same bridge request between list and approve.
|
||||
// Reconnect the MCP client when no replacement request is left to approve.
|
||||
}
|
||||
}
|
||||
if (!pairingState) {
|
||||
return false;
|
||||
}
|
||||
const pendingRequest = pairingState.pending?.find((entry) => entry.role === "operator");
|
||||
if (!pendingRequest?.requestId) {
|
||||
return false;
|
||||
}
|
||||
await gateway.request("device.pair.approve", { requestId: pendingRequest.requestId });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -198,9 +198,7 @@ describe("bundled plugin install/uninstall probe", () => {
|
||||
"qa-channel",
|
||||
),
|
||||
).not.toThrow();
|
||||
expect(() =>
|
||||
runtimeSmoke.assertChannelVisible({}, "qa-channel", "qa-channel"),
|
||||
).toThrow(
|
||||
expect(() => runtimeSmoke.assertChannelVisible({}, "qa-channel", "qa-channel")).toThrow(
|
||||
"Runtime channel status missing manifest channel qa-channel for qa-channel",
|
||||
);
|
||||
});
|
||||
@@ -380,11 +378,15 @@ describe("bundled plugin install/uninstall probe", () => {
|
||||
);
|
||||
|
||||
await expect(
|
||||
runtimeSmoke.rpcCall("health", {}, {
|
||||
entrypoint,
|
||||
env: { OPENCLAW_TEST_RPC_STATE_PATH: statePath },
|
||||
port: 19001,
|
||||
}),
|
||||
runtimeSmoke.rpcCall(
|
||||
"health",
|
||||
{},
|
||||
{
|
||||
entrypoint,
|
||||
env: { OPENCLAW_TEST_RPC_STATE_PATH: statePath },
|
||||
port: 19001,
|
||||
},
|
||||
),
|
||||
).resolves.toEqual({ status: "ok" });
|
||||
|
||||
const rpcStateDir = fs.readFileSync(statePath, "utf8");
|
||||
@@ -515,6 +517,28 @@ describe("bundled plugin install/uninstall probe", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("treats channel env vars as runtime smoke config requirements", () => {
|
||||
const root = makePackageRoot();
|
||||
writePluginManifest(root, "dist-runtime/extensions/clickclack", {
|
||||
id: "clickclack",
|
||||
channelEnvVars: { clickclack: ["CLICKCLACK_BOT_TOKEN"] },
|
||||
});
|
||||
writePluginsList(root, [
|
||||
{
|
||||
id: "clickclack",
|
||||
origin: "bundled",
|
||||
rootDir: path.join(root, "dist-runtime", "extensions", "clickclack"),
|
||||
},
|
||||
]);
|
||||
|
||||
const result = runProbe(root);
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stdout.trim()).toBe(
|
||||
`clickclack\tclickclack\t1\t${path.join(root, "dist-runtime", "extensions", "clickclack")}`,
|
||||
);
|
||||
});
|
||||
|
||||
it("does not select source-only bundled plugins for package-backed sweeps", () => {
|
||||
const root = makePackageRoot();
|
||||
writePluginManifest(root, "extensions/qa-channel", {
|
||||
|
||||
Reference in New Issue
Block a user