mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 13:41:37 +00:00
chore(ci): fix main check blockers and stabilize tests
This commit is contained in:
@@ -113,6 +113,35 @@ import type { OpenClawPluginServiceContext } from "openclaw/plugin-sdk";
|
|||||||
import { emitDiagnosticEvent } from "openclaw/plugin-sdk";
|
import { emitDiagnosticEvent } from "openclaw/plugin-sdk";
|
||||||
import { createDiagnosticsOtelService } from "./service.js";
|
import { createDiagnosticsOtelService } from "./service.js";
|
||||||
|
|
||||||
|
function createLogger() {
|
||||||
|
return {
|
||||||
|
info: vi.fn(),
|
||||||
|
warn: vi.fn(),
|
||||||
|
error: vi.fn(),
|
||||||
|
debug: vi.fn(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTraceOnlyContext(endpoint: string): OpenClawPluginServiceContext {
|
||||||
|
return {
|
||||||
|
config: {
|
||||||
|
diagnostics: {
|
||||||
|
enabled: true,
|
||||||
|
otel: {
|
||||||
|
enabled: true,
|
||||||
|
endpoint,
|
||||||
|
protocol: "http/protobuf",
|
||||||
|
traces: true,
|
||||||
|
metrics: false,
|
||||||
|
logs: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
logger: createLogger(),
|
||||||
|
stateDir: "/tmp/openclaw-diagnostics-otel-test",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
describe("diagnostics-otel service", () => {
|
describe("diagnostics-otel service", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
telemetryState.counters.clear();
|
telemetryState.counters.clear();
|
||||||
@@ -151,12 +180,7 @@ describe("diagnostics-otel service", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
logger: {
|
logger: createLogger(),
|
||||||
info: vi.fn(),
|
|
||||||
warn: vi.fn(),
|
|
||||||
error: vi.fn(),
|
|
||||||
debug: vi.fn(),
|
|
||||||
},
|
|
||||||
stateDir: "/tmp/openclaw-diagnostics-otel-test",
|
stateDir: "/tmp/openclaw-diagnostics-otel-test",
|
||||||
};
|
};
|
||||||
await service.start(ctx);
|
await service.start(ctx);
|
||||||
@@ -236,97 +260,41 @@ describe("diagnostics-otel service", () => {
|
|||||||
|
|
||||||
test("appends signal path when endpoint contains non-signal /v1 segment", async () => {
|
test("appends signal path when endpoint contains non-signal /v1 segment", async () => {
|
||||||
const service = createDiagnosticsOtelService();
|
const service = createDiagnosticsOtelService();
|
||||||
await service.start({
|
const ctx = createTraceOnlyContext("https://www.comet.com/opik/api/v1/private/otel");
|
||||||
config: {
|
await service.start(ctx);
|
||||||
diagnostics: {
|
|
||||||
enabled: true,
|
|
||||||
otel: {
|
|
||||||
enabled: true,
|
|
||||||
endpoint: "https://www.comet.com/opik/api/v1/private/otel",
|
|
||||||
protocol: "http/protobuf",
|
|
||||||
traces: true,
|
|
||||||
metrics: false,
|
|
||||||
logs: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
|
|
||||||
});
|
|
||||||
|
|
||||||
const options = traceExporterCtor.mock.calls[0]?.[0] as { url?: string } | undefined;
|
const options = traceExporterCtor.mock.calls[0]?.[0] as { url?: string } | undefined;
|
||||||
expect(options?.url).toBe("https://www.comet.com/opik/api/v1/private/otel/v1/traces");
|
expect(options?.url).toBe("https://www.comet.com/opik/api/v1/private/otel/v1/traces");
|
||||||
await service.stop?.();
|
await service.stop?.(ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("keeps already signal-qualified endpoint unchanged", async () => {
|
test("keeps already signal-qualified endpoint unchanged", async () => {
|
||||||
const service = createDiagnosticsOtelService();
|
const service = createDiagnosticsOtelService();
|
||||||
await service.start({
|
const ctx = createTraceOnlyContext("https://collector.example.com/v1/traces");
|
||||||
config: {
|
await service.start(ctx);
|
||||||
diagnostics: {
|
|
||||||
enabled: true,
|
|
||||||
otel: {
|
|
||||||
enabled: true,
|
|
||||||
endpoint: "https://collector.example.com/v1/traces",
|
|
||||||
protocol: "http/protobuf",
|
|
||||||
traces: true,
|
|
||||||
metrics: false,
|
|
||||||
logs: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
|
|
||||||
});
|
|
||||||
|
|
||||||
const options = traceExporterCtor.mock.calls[0]?.[0] as { url?: string } | undefined;
|
const options = traceExporterCtor.mock.calls[0]?.[0] as { url?: string } | undefined;
|
||||||
expect(options?.url).toBe("https://collector.example.com/v1/traces");
|
expect(options?.url).toBe("https://collector.example.com/v1/traces");
|
||||||
await service.stop?.();
|
await service.stop?.(ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("keeps signal-qualified endpoint unchanged when it has query params", async () => {
|
test("keeps signal-qualified endpoint unchanged when it has query params", async () => {
|
||||||
const service = createDiagnosticsOtelService();
|
const service = createDiagnosticsOtelService();
|
||||||
await service.start({
|
const ctx = createTraceOnlyContext("https://collector.example.com/v1/traces?timeout=30s");
|
||||||
config: {
|
await service.start(ctx);
|
||||||
diagnostics: {
|
|
||||||
enabled: true,
|
|
||||||
otel: {
|
|
||||||
enabled: true,
|
|
||||||
endpoint: "https://collector.example.com/v1/traces?timeout=30s",
|
|
||||||
protocol: "http/protobuf",
|
|
||||||
traces: true,
|
|
||||||
metrics: false,
|
|
||||||
logs: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
|
|
||||||
});
|
|
||||||
|
|
||||||
const options = traceExporterCtor.mock.calls[0]?.[0] as { url?: string } | undefined;
|
const options = traceExporterCtor.mock.calls[0]?.[0] as { url?: string } | undefined;
|
||||||
expect(options?.url).toBe("https://collector.example.com/v1/traces?timeout=30s");
|
expect(options?.url).toBe("https://collector.example.com/v1/traces?timeout=30s");
|
||||||
await service.stop?.();
|
await service.stop?.(ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("keeps signal-qualified endpoint unchanged when signal path casing differs", async () => {
|
test("keeps signal-qualified endpoint unchanged when signal path casing differs", async () => {
|
||||||
const service = createDiagnosticsOtelService();
|
const service = createDiagnosticsOtelService();
|
||||||
await service.start({
|
const ctx = createTraceOnlyContext("https://collector.example.com/v1/Traces");
|
||||||
config: {
|
await service.start(ctx);
|
||||||
diagnostics: {
|
|
||||||
enabled: true,
|
|
||||||
otel: {
|
|
||||||
enabled: true,
|
|
||||||
endpoint: "https://collector.example.com/v1/Traces",
|
|
||||||
protocol: "http/protobuf",
|
|
||||||
traces: true,
|
|
||||||
metrics: false,
|
|
||||||
logs: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
|
|
||||||
});
|
|
||||||
|
|
||||||
const options = traceExporterCtor.mock.calls[0]?.[0] as { url?: string } | undefined;
|
const options = traceExporterCtor.mock.calls[0]?.[0] as { url?: string } | undefined;
|
||||||
expect(options?.url).toBe("https://collector.example.com/v1/Traces");
|
expect(options?.url).toBe("https://collector.example.com/v1/Traces");
|
||||||
await service.stop?.();
|
await service.stop?.(ctx);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ import { spawn } from "node:child_process";
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import type { OpenClawConfig } from "../../config/config.js";
|
|
||||||
import type { MsgContext, TemplateContext } from "../templating.js";
|
|
||||||
import { assertSandboxPath } from "../../agents/sandbox-paths.js";
|
import { assertSandboxPath } from "../../agents/sandbox-paths.js";
|
||||||
import { ensureSandboxWorkspaceForSession } from "../../agents/sandbox.js";
|
import { ensureSandboxWorkspaceForSession } from "../../agents/sandbox.js";
|
||||||
|
import type { OpenClawConfig } from "../../config/config.js";
|
||||||
import { logVerbose } from "../../globals.js";
|
import { logVerbose } from "../../globals.js";
|
||||||
import { normalizeScpRemoteHost } from "../../infra/scp-host.js";
|
import { normalizeScpRemoteHost } from "../../infra/scp-host.js";
|
||||||
import { getMediaDir } from "../../media/store.js";
|
import { getMediaDir } from "../../media/store.js";
|
||||||
import { CONFIG_DIR } from "../../utils.js";
|
import { CONFIG_DIR } from "../../utils.js";
|
||||||
|
import type { MsgContext, TemplateContext } from "../templating.js";
|
||||||
|
|
||||||
export async function stageSandboxMedia(params: {
|
export async function stageSandboxMedia(params: {
|
||||||
ctx: MsgContext;
|
ctx: MsgContext;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
const state = vi.hoisted(() => ({
|
const state = vi.hoisted(() => ({
|
||||||
launchctlCalls: [] as string[][],
|
launchctlCalls: [] as string[][],
|
||||||
listOutput: "",
|
listOutput: "",
|
||||||
|
bootstrapError: "",
|
||||||
dirs: new Set<string>(),
|
dirs: new Set<string>(),
|
||||||
files: new Map<string, string>(),
|
files: new Map<string, string>(),
|
||||||
}));
|
}));
|
||||||
@@ -33,6 +34,9 @@ vi.mock("./exec-file.js", () => ({
|
|||||||
if (call[0] === "list") {
|
if (call[0] === "list") {
|
||||||
return { stdout: state.listOutput, stderr: "", code: 0 };
|
return { stdout: state.listOutput, stderr: "", code: 0 };
|
||||||
}
|
}
|
||||||
|
if (call[0] === "bootstrap" && state.bootstrapError) {
|
||||||
|
return { stdout: "", stderr: state.bootstrapError, code: 1 };
|
||||||
|
}
|
||||||
return { stdout: "", stderr: "", code: 0 };
|
return { stdout: "", stderr: "", code: 0 };
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
@@ -66,6 +70,7 @@ vi.mock("node:fs/promises", async (importOriginal) => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
state.launchctlCalls.length = 0;
|
state.launchctlCalls.length = 0;
|
||||||
state.listOutput = "";
|
state.listOutput = "";
|
||||||
|
state.bootstrapError = "";
|
||||||
state.dirs.clear();
|
state.dirs.clear();
|
||||||
state.files.clear();
|
state.files.clear();
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
@@ -173,64 +178,24 @@ describe("launchd install", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("shows actionable guidance when launchctl gui domain does not support bootstrap", async () => {
|
it("shows actionable guidance when launchctl gui domain does not support bootstrap", async () => {
|
||||||
const originalPath = process.env.PATH;
|
state.bootstrapError = "Bootstrap failed: 125: Domain does not support specified action";
|
||||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-launchctl-test-"));
|
const env: Record<string, string | undefined> = {
|
||||||
|
HOME: "/Users/test",
|
||||||
|
OPENCLAW_PROFILE: "default",
|
||||||
|
};
|
||||||
|
let message = "";
|
||||||
try {
|
try {
|
||||||
const binDir = path.join(tmpDir, "bin");
|
await installLaunchAgent({
|
||||||
const homeDir = path.join(tmpDir, "home");
|
env,
|
||||||
await fs.mkdir(binDir, { recursive: true });
|
stdout: new PassThrough(),
|
||||||
await fs.mkdir(homeDir, { recursive: true });
|
programArguments: ["node", "-e", "process.exit(0)"],
|
||||||
|
});
|
||||||
const stubJsPath = path.join(binDir, "launchctl.js");
|
} catch (error) {
|
||||||
await fs.writeFile(
|
message = String(error);
|
||||||
stubJsPath,
|
|
||||||
[
|
|
||||||
"const args = process.argv.slice(2);",
|
|
||||||
'if (args[0] === "bootstrap") {',
|
|
||||||
' process.stderr.write("Bootstrap failed: 125: Domain does not support specified action\\n");',
|
|
||||||
" process.exit(1);",
|
|
||||||
"}",
|
|
||||||
"process.exit(0);",
|
|
||||||
"",
|
|
||||||
].join("\n"),
|
|
||||||
"utf8",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (process.platform === "win32") {
|
|
||||||
await fs.writeFile(
|
|
||||||
path.join(binDir, "launchctl.cmd"),
|
|
||||||
`@echo off\r\nnode "%~dp0\\launchctl.js" %*\r\n`,
|
|
||||||
"utf8",
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const shPath = path.join(binDir, "launchctl");
|
|
||||||
await fs.writeFile(shPath, `#!/bin/sh\nnode "$(dirname "$0")/launchctl.js" "$@"\n`, "utf8");
|
|
||||||
await fs.chmod(shPath, 0o755);
|
|
||||||
}
|
|
||||||
|
|
||||||
process.env.PATH = `${binDir}${path.delimiter}${originalPath ?? ""}`;
|
|
||||||
|
|
||||||
const env: Record<string, string | undefined> = {
|
|
||||||
HOME: homeDir,
|
|
||||||
OPENCLAW_PROFILE: "default",
|
|
||||||
};
|
|
||||||
let message = "";
|
|
||||||
try {
|
|
||||||
await installLaunchAgent({
|
|
||||||
env,
|
|
||||||
stdout: new PassThrough(),
|
|
||||||
programArguments: ["node", "-e", "process.exit(0)"],
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
message = String(error);
|
|
||||||
}
|
|
||||||
expect(message).toContain("logged-in macOS GUI session");
|
|
||||||
expect(message).toContain("wrong user (including sudo)");
|
|
||||||
expect(message).toContain("https://docs.openclaw.ai/gateway");
|
|
||||||
} finally {
|
|
||||||
process.env.PATH = originalPath;
|
|
||||||
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
||||||
}
|
}
|
||||||
|
expect(message).toContain("logged-in macOS GUI session");
|
||||||
|
expect(message).toContain("wrong user (including sudo)");
|
||||||
|
expect(message).toContain("https://docs.openclaw.ai/gateway");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import type { IMessagePayload, MonitorIMessageOpts } from "./types.js";
|
|
||||||
import { resolveHumanDelayConfig } from "../../agents/identity.js";
|
import { resolveHumanDelayConfig } from "../../agents/identity.js";
|
||||||
import { resolveTextChunkLimit } from "../../auto-reply/chunk.js";
|
import { resolveTextChunkLimit } from "../../auto-reply/chunk.js";
|
||||||
import { hasControlCommand } from "../../auto-reply/command-detection.js";
|
import { hasControlCommand } from "../../auto-reply/command-detection.js";
|
||||||
@@ -41,6 +40,7 @@ import {
|
|||||||
} from "./inbound-processing.js";
|
} from "./inbound-processing.js";
|
||||||
import { parseIMessageNotification } from "./parse-notification.js";
|
import { parseIMessageNotification } from "./parse-notification.js";
|
||||||
import { normalizeAllowList, resolveRuntime } from "./runtime.js";
|
import { normalizeAllowList, resolveRuntime } from "./runtime.js";
|
||||||
|
import type { IMessagePayload, MonitorIMessageOpts } from "./types.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to detect remote host from an SSH wrapper script like:
|
* Try to detect remote host from an SSH wrapper script like:
|
||||||
|
|||||||
Reference in New Issue
Block a user