mirror of https://github.com/openclaw/openclaw.git
test: fix CI type regressions
This commit is contained in:
parent
d17490ff54
commit
60d308cff0
|
|
@ -161,6 +161,12 @@ export type ScheduledTaskInfo = {
|
||||||
lastRunResult?: string;
|
lastRunResult?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function hasListenerPid<T extends { pid?: number | null }>(
|
||||||
|
listener: T,
|
||||||
|
): listener is T & { pid: number } {
|
||||||
|
return typeof listener.pid === "number";
|
||||||
|
}
|
||||||
|
|
||||||
export function parseSchtasksQuery(output: string): ScheduledTaskInfo {
|
export function parseSchtasksQuery(output: string): ScheduledTaskInfo {
|
||||||
const entries = parseKeyValueOutput(output, ":");
|
const entries = parseKeyValueOutput(output, ":");
|
||||||
const info: ScheduledTaskInfo = {};
|
const info: ScheduledTaskInfo = {};
|
||||||
|
|
@ -388,7 +394,7 @@ async function resolveScheduledTaskGatewayListenerPids(port: number): Promise<nu
|
||||||
new Set(
|
new Set(
|
||||||
diagnostics.listeners
|
diagnostics.listeners
|
||||||
.map((listener) => listener.pid)
|
.map((listener) => listener.pid)
|
||||||
.filter((pid): pid is number => Number.isFinite(pid) && pid > 0),
|
.filter((pid): pid is number => typeof pid === "number" && Number.isFinite(pid) && pid > 0),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -472,7 +478,7 @@ async function terminateBusyPortListeners(port: number): Promise<number[]> {
|
||||||
new Set(
|
new Set(
|
||||||
diagnostics.listeners
|
diagnostics.listeners
|
||||||
.map((listener) => listener.pid)
|
.map((listener) => listener.pid)
|
||||||
.filter((pid): pid is number => Number.isFinite(pid) && pid > 0),
|
.filter((pid): pid is number => typeof pid === "number" && Number.isFinite(pid) && pid > 0),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
for (const pid of pids) {
|
for (const pid of pids) {
|
||||||
|
|
@ -496,7 +502,7 @@ async function resolveFallbackRuntime(env: GatewayServiceEnv): Promise<GatewaySe
|
||||||
detail: `Startup-folder login item installed; could not inspect port ${port}.`,
|
detail: `Startup-folder login item installed; could not inspect port ${port}.`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const listener = diagnostics.listeners.find((item) => typeof item.pid === "number");
|
const listener = diagnostics.listeners.find(hasListenerPid);
|
||||||
return {
|
return {
|
||||||
status: diagnostics.status === "busy" ? "running" : "stopped",
|
status: diagnostics.status === "busy" ? "running" : "stopped",
|
||||||
...(listener?.pid ? { pid: listener.pid } : {}),
|
...(listener?.pid ? { pid: listener.pid } : {}),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import type { DedupeEntry } from "../server-shared.js";
|
||||||
import {
|
import {
|
||||||
__testing,
|
__testing,
|
||||||
readTerminalSnapshotFromGatewayDedupe,
|
readTerminalSnapshotFromGatewayDedupe,
|
||||||
|
|
@ -8,7 +9,7 @@ import {
|
||||||
|
|
||||||
describe("agent wait dedupe helper", () => {
|
describe("agent wait dedupe helper", () => {
|
||||||
function setRunEntry(params: {
|
function setRunEntry(params: {
|
||||||
dedupe: Map<unknown, unknown>;
|
dedupe: Map<string, DedupeEntry>;
|
||||||
kind: "agent" | "chat";
|
kind: "agent" | "chat";
|
||||||
runId: string;
|
runId: string;
|
||||||
ts?: number;
|
ts?: number;
|
||||||
|
|
|
||||||
|
|
@ -251,7 +251,7 @@ describe("resolveGatewayRuntimeConfig", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("HTTP security headers", () => {
|
describe("HTTP security headers", () => {
|
||||||
it.each([
|
const cases = [
|
||||||
{
|
{
|
||||||
name: "resolves strict transport security headers from config",
|
name: "resolves strict transport security headers from config",
|
||||||
strictTransportSecurity: " max-age=31536000; includeSubDomains ",
|
strictTransportSecurity: " max-age=31536000; includeSubDomains ",
|
||||||
|
|
@ -267,7 +267,13 @@ describe("resolveGatewayRuntimeConfig", () => {
|
||||||
strictTransportSecurity: " ",
|
strictTransportSecurity: " ",
|
||||||
expected: undefined,
|
expected: undefined,
|
||||||
},
|
},
|
||||||
])("$name", async ({ strictTransportSecurity, expected }) => {
|
] satisfies ReadonlyArray<{
|
||||||
|
name: string;
|
||||||
|
strictTransportSecurity: string | false;
|
||||||
|
expected: string | undefined;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
it.each(cases)("$name", async ({ strictTransportSecurity, expected }) => {
|
||||||
const result = await resolveGatewayRuntimeConfig({
|
const result = await resolveGatewayRuntimeConfig({
|
||||||
cfg: {
|
cfg: {
|
||||||
gateway: {
|
gateway: {
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ type TalkConfigPayload = {
|
||||||
ui?: { seamColor?: string };
|
ui?: { seamColor?: string };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
type TalkConfig = NonNullable<NonNullable<TalkConfigPayload["config"]>["talk"]>;
|
||||||
const TALK_CONFIG_DEVICE_PATH = path.join(
|
const TALK_CONFIG_DEVICE_PATH = path.join(
|
||||||
os.tmpdir(),
|
os.tmpdir(),
|
||||||
`openclaw-talk-config-device-${process.pid}.json`,
|
`openclaw-talk-config-device-${process.pid}.json`,
|
||||||
|
|
@ -95,7 +96,7 @@ async function fetchTalkConfig(
|
||||||
}
|
}
|
||||||
|
|
||||||
function expectElevenLabsTalkConfig(
|
function expectElevenLabsTalkConfig(
|
||||||
talk: TalkConfigPayload["config"] extends { talk?: infer T } ? T : never,
|
talk: TalkConfig | undefined,
|
||||||
expected: {
|
expected: {
|
||||||
voiceId?: string;
|
voiceId?: string;
|
||||||
apiKey?: string | SecretRef;
|
apiKey?: string | SecretRef;
|
||||||
|
|
|
||||||
|
|
@ -91,13 +91,13 @@ describe("agent-events sequencing", () => {
|
||||||
isControlUiVisible: true,
|
isControlUiVisible: true,
|
||||||
});
|
});
|
||||||
registerAgentRunContext("run-ctx", {
|
registerAgentRunContext("run-ctx", {
|
||||||
verboseLevel: "high",
|
verboseLevel: "full",
|
||||||
isHeartbeat: true,
|
isHeartbeat: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(getAgentRunContext("run-ctx")).toEqual({
|
expect(getAgentRunContext("run-ctx")).toEqual({
|
||||||
sessionKey: "session-main",
|
sessionKey: "session-main",
|
||||||
verboseLevel: "high",
|
verboseLevel: "full",
|
||||||
isHeartbeat: true,
|
isHeartbeat: true,
|
||||||
isControlUiVisible: true,
|
isControlUiVisible: true,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,10 @@ describe("error helpers", () => {
|
||||||
child.cause = root;
|
child.cause = root;
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
collectErrorGraphCandidates(root, (current) => [current.cause, ...(current.errors ?? [])]),
|
collectErrorGraphCandidates(root, (current) => [
|
||||||
|
current.cause,
|
||||||
|
...((current as { errors?: unknown[] }).errors ?? []),
|
||||||
|
]),
|
||||||
).toEqual([root, child, leaf]);
|
).toEqual([root, child, leaf]);
|
||||||
expect(collectErrorGraphCandidates(null)).toEqual([]);
|
expect(collectErrorGraphCandidates(null)).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ describe("format-datetime", () => {
|
||||||
formatToParts: () => {
|
formatToParts: () => {
|
||||||
throw new Error("boom");
|
throw new Error("boom");
|
||||||
},
|
},
|
||||||
} as Intl.DateTimeFormat;
|
} as unknown as Intl.DateTimeFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
vi.spyOn(Intl, "DateTimeFormat").mockImplementation(
|
vi.spyOn(Intl, "DateTimeFormat").mockImplementation(
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import {
|
||||||
resolveTimedInstallModeOptions,
|
resolveTimedInstallModeOptions,
|
||||||
} from "./install-mode-options.js";
|
} from "./install-mode-options.js";
|
||||||
|
|
||||||
|
type LoggerKey = "default" | "explicit";
|
||||||
|
|
||||||
describe("install mode option helpers", () => {
|
describe("install mode option helpers", () => {
|
||||||
it.each([
|
it.each([
|
||||||
{
|
{
|
||||||
|
|
@ -21,11 +23,15 @@ describe("install mode option helpers", () => {
|
||||||
params: { mode: "update" as const, dryRun: false },
|
params: { mode: "update" as const, dryRun: false },
|
||||||
expected: { loggerKey: "default", mode: "update", dryRun: false },
|
expected: { loggerKey: "default", mode: "update", dryRun: false },
|
||||||
},
|
},
|
||||||
])("$name", ({ params, expected }) => {
|
] satisfies Array<{
|
||||||
|
name: string;
|
||||||
|
params: { loggerKey?: LoggerKey; mode?: "install" | "update"; dryRun?: boolean };
|
||||||
|
expected: { loggerKey: LoggerKey; mode: "install" | "update"; dryRun: boolean };
|
||||||
|
}>)("$name", ({ params, expected }) => {
|
||||||
const loggers = {
|
const loggers = {
|
||||||
default: { warn: (_message: string) => {} },
|
default: { warn: (_message: string) => {} },
|
||||||
explicit: { warn: (_message: string) => {} },
|
explicit: { warn: (_message: string) => {} },
|
||||||
};
|
} satisfies Record<LoggerKey, { warn: (_message: string) => void }>;
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
resolveInstallModeOptions(
|
resolveInstallModeOptions(
|
||||||
|
|
|
||||||
|
|
@ -462,7 +462,14 @@ describe("resolveSessionDeliveryTarget", () => {
|
||||||
expectedChannel: "none",
|
expectedChannel: "none",
|
||||||
expectedReason: "dm-blocked",
|
expectedReason: "dm-blocked",
|
||||||
},
|
},
|
||||||
])("$name", ({ name, entry, directPolicy, expectedChannel, expectedTo, expectedReason }) => {
|
] satisfies Array<{
|
||||||
|
name: string;
|
||||||
|
entry: NonNullable<Parameters<typeof resolveHeartbeatDeliveryTarget>[0]["entry"]>;
|
||||||
|
directPolicy?: "allow" | "block";
|
||||||
|
expectedChannel: string;
|
||||||
|
expectedTo?: string;
|
||||||
|
expectedReason?: string;
|
||||||
|
}>)("$name", ({ name, entry, directPolicy, expectedChannel, expectedTo, expectedReason }) => {
|
||||||
expectHeartbeatTarget({
|
expectHeartbeatTarget({
|
||||||
name,
|
name,
|
||||||
entry,
|
entry,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||||
import { NON_ENV_SECRETREF_MARKER } from "../agents/model-auth-markers.js";
|
import { NON_ENV_SECRETREF_MARKER } from "../agents/model-auth-markers.js";
|
||||||
import { resolveProviderAuths } from "./provider-usage.auth.js";
|
import { resolveProviderAuths, type ProviderAuth } from "./provider-usage.auth.js";
|
||||||
|
|
||||||
describe("resolveProviderAuths key normalization", () => {
|
describe("resolveProviderAuths key normalization", () => {
|
||||||
let suiteRoot = "";
|
let suiteRoot = "";
|
||||||
|
|
@ -214,7 +214,12 @@ describe("resolveProviderAuths key normalization", () => {
|
||||||
},
|
},
|
||||||
expected: [{ provider: "minimax", token: "code-plan-key" }],
|
expected: [{ provider: "minimax", token: "code-plan-key" }],
|
||||||
},
|
},
|
||||||
])("$name", async ({ providers, env, expected }) => {
|
] satisfies Array<{
|
||||||
|
name: string;
|
||||||
|
providers: readonly Parameters<typeof resolveProviderAuths>[0]["providers"][number][];
|
||||||
|
env: Record<string, string | undefined>;
|
||||||
|
expected: ProviderAuth[];
|
||||||
|
}>)("$name", async ({ providers, env, expected }) => {
|
||||||
await expectResolvedAuthsFromSuiteHome({ providers: [...providers], env, expected });
|
await expectResolvedAuthsFromSuiteHome({ providers: [...providers], env, expected });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,10 +32,11 @@ describe("provider usage fetch shared helpers", () => {
|
||||||
it("forwards request init and clears the timeout on success", async () => {
|
it("forwards request init and clears the timeout on success", async () => {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
const clearTimeoutSpy = vi.spyOn(globalThis, "clearTimeout");
|
const clearTimeoutSpy = vi.spyOn(globalThis, "clearTimeout");
|
||||||
const fetchFn = vi.fn(
|
const fetchFnMock = vi.fn(
|
||||||
async (_url: string, init?: RequestInit) =>
|
async (_input: URL | RequestInfo, init?: RequestInit) =>
|
||||||
new Response(JSON.stringify({ aborted: init?.signal?.aborted ?? false }), { status: 200 }),
|
new Response(JSON.stringify({ aborted: init?.signal?.aborted ?? false }), { status: 200 }),
|
||||||
);
|
);
|
||||||
|
const fetchFn = fetchFnMock as typeof fetch;
|
||||||
|
|
||||||
const response = await fetchJson(
|
const response = await fetchJson(
|
||||||
"https://example.com/usage",
|
"https://example.com/usage",
|
||||||
|
|
@ -47,7 +48,7 @@ describe("provider usage fetch shared helpers", () => {
|
||||||
fetchFn,
|
fetchFn,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(fetchFn).toHaveBeenCalledWith(
|
expect(fetchFnMock).toHaveBeenCalledWith(
|
||||||
"https://example.com/usage",
|
"https://example.com/usage",
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -62,14 +63,15 @@ describe("provider usage fetch shared helpers", () => {
|
||||||
it("aborts timed out requests and clears the timer on rejection", async () => {
|
it("aborts timed out requests and clears the timer on rejection", async () => {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
const clearTimeoutSpy = vi.spyOn(globalThis, "clearTimeout");
|
const clearTimeoutSpy = vi.spyOn(globalThis, "clearTimeout");
|
||||||
const fetchFn = vi.fn(
|
const fetchFnMock = vi.fn(
|
||||||
(_url: string, init?: RequestInit) =>
|
(_input: URL | RequestInfo, init?: RequestInit) =>
|
||||||
new Promise<Response>((_, reject) => {
|
new Promise<Response>((_, reject) => {
|
||||||
init?.signal?.addEventListener("abort", () => reject(new Error("aborted by timeout")), {
|
init?.signal?.addEventListener("abort", () => reject(new Error("aborted by timeout")), {
|
||||||
once: true,
|
once: true,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
const fetchFn = fetchFnMock as typeof fetch;
|
||||||
|
|
||||||
const request = fetchJson("https://example.com/usage", {}, 50, fetchFn);
|
const request = fetchJson("https://example.com/usage", {}, 50, fetchFn);
|
||||||
const rejection = expect(request).rejects.toThrow("aborted by timeout");
|
const rejection = expect(request).rejects.toThrow("aborted by timeout");
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,11 @@ import path from "node:path";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { openVerifiedFileSync } from "./safe-open-sync.js";
|
import { openVerifiedFileSync } from "./safe-open-sync.js";
|
||||||
|
|
||||||
|
type SafeOpenSyncFs = NonNullable<Parameters<typeof openVerifiedFileSync>[0]["ioFs"]>;
|
||||||
|
type SafeOpenSyncLstatSync = SafeOpenSyncFs["lstatSync"];
|
||||||
|
type SafeOpenSyncRealpathSync = SafeOpenSyncFs["realpathSync"];
|
||||||
|
type SafeOpenSyncFstatSync = SafeOpenSyncFs["fstatSync"];
|
||||||
|
|
||||||
async function withTempDir<T>(prefix: string, run: (dir: string) => Promise<T>): Promise<T> {
|
async function withTempDir<T>(prefix: string, run: (dir: string) => Promise<T>): Promise<T> {
|
||||||
const dir = await fsp.mkdtemp(path.join(os.tmpdir(), prefix));
|
const dir = await fsp.mkdtemp(path.join(os.tmpdir(), prefix));
|
||||||
try {
|
try {
|
||||||
|
|
@ -33,6 +38,20 @@ function mockStat(params: {
|
||||||
} as unknown as fs.Stats;
|
} as unknown as fs.Stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mockRealpathSync(result: string): SafeOpenSyncRealpathSync {
|
||||||
|
const resolvePath = ((_: fs.PathLike) => result) as SafeOpenSyncRealpathSync;
|
||||||
|
resolvePath.native = ((_: fs.PathLike) => result) as typeof resolvePath.native;
|
||||||
|
return resolvePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockLstatSync(read: (filePath: fs.PathLike) => fs.Stats): SafeOpenSyncLstatSync {
|
||||||
|
return ((filePath: fs.PathLike) => read(filePath)) as unknown as SafeOpenSyncLstatSync;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockFstatSync(stat: fs.Stats): SafeOpenSyncFstatSync {
|
||||||
|
return ((_: number) => stat) as unknown as SafeOpenSyncFstatSync;
|
||||||
|
}
|
||||||
|
|
||||||
describe("openVerifiedFileSync", () => {
|
describe("openVerifiedFileSync", () => {
|
||||||
it("returns a path error for missing files", async () => {
|
it("returns a path error for missing files", async () => {
|
||||||
await withTempDir("openclaw-safe-open-", async (root) => {
|
await withTempDir("openclaw-safe-open-", async (root) => {
|
||||||
|
|
@ -115,15 +134,16 @@ describe("openVerifiedFileSync", () => {
|
||||||
closed.push(fd);
|
closed.push(fd);
|
||||||
};
|
};
|
||||||
const closed: number[] = [];
|
const closed: number[] = [];
|
||||||
const ioFs = {
|
const ioFs: SafeOpenSyncFs = {
|
||||||
constants: fs.constants,
|
constants: fs.constants,
|
||||||
lstatSync: (filePath: string) =>
|
lstatSync: mockLstatSync((filePath) =>
|
||||||
filePath === "/real/file.txt"
|
String(filePath) === "/real/file.txt"
|
||||||
? mockStat({ isFile: true, size: 1, dev: 1, ino: 1 })
|
? mockStat({ isFile: true, size: 1, dev: 1, ino: 1 })
|
||||||
: mockStat({ isFile: false }),
|
: mockStat({ isFile: false }),
|
||||||
realpathSync: () => "/real/file.txt",
|
),
|
||||||
|
realpathSync: mockRealpathSync("/real/file.txt"),
|
||||||
openSync: () => 42,
|
openSync: () => 42,
|
||||||
fstatSync: () => mockStat({ isFile: true, size: 1, dev: 2, ino: 1 }),
|
fstatSync: mockFstatSync(mockStat({ isFile: true, size: 1, dev: 2, ino: 1 })),
|
||||||
closeSync,
|
closeSync,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -139,16 +159,16 @@ describe("openVerifiedFileSync", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("reports non-path filesystem failures as io errors", () => {
|
it("reports non-path filesystem failures as io errors", () => {
|
||||||
const ioFs = {
|
const ioFs: SafeOpenSyncFs = {
|
||||||
constants: fs.constants,
|
constants: fs.constants,
|
||||||
lstatSync: () => {
|
lstatSync: () => {
|
||||||
const err = new Error("permission denied") as NodeJS.ErrnoException;
|
const err = new Error("permission denied") as NodeJS.ErrnoException;
|
||||||
err.code = "EACCES";
|
err.code = "EACCES";
|
||||||
throw err;
|
throw err;
|
||||||
},
|
},
|
||||||
realpathSync: () => "/real/file.txt",
|
realpathSync: mockRealpathSync("/real/file.txt"),
|
||||||
openSync: () => 42,
|
openSync: () => 42,
|
||||||
fstatSync: () => mockStat({ isFile: true }),
|
fstatSync: mockFstatSync(mockStat({ isFile: true })),
|
||||||
closeSync: () => {},
|
closeSync: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import {
|
||||||
normalizeUpdateChannel,
|
normalizeUpdateChannel,
|
||||||
resolveEffectiveUpdateChannel,
|
resolveEffectiveUpdateChannel,
|
||||||
resolveUpdateChannelDisplay,
|
resolveUpdateChannelDisplay,
|
||||||
|
type UpdateChannel,
|
||||||
|
type UpdateChannelSource,
|
||||||
} from "./update-channels.js";
|
} from "./update-channels.js";
|
||||||
|
|
||||||
describe("update-channels tag detection", () => {
|
describe("update-channels tag detection", () => {
|
||||||
|
|
@ -32,9 +34,12 @@ describe("normalizeUpdateChannel", () => {
|
||||||
{ value: " nightly ", expected: null },
|
{ value: " nightly ", expected: null },
|
||||||
{ value: null, expected: null },
|
{ value: null, expected: null },
|
||||||
{ value: undefined, expected: null },
|
{ value: undefined, expected: null },
|
||||||
])("normalizes %j", ({ value, expected }) => {
|
] satisfies Array<{ value: string | null | undefined; expected: UpdateChannel | null }>)(
|
||||||
expect(normalizeUpdateChannel(value)).toBe(expected);
|
"normalizes %j",
|
||||||
});
|
({ value, expected }) => {
|
||||||
|
expect(normalizeUpdateChannel(value)).toBe(expected);
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("channelToNpmTag", () => {
|
describe("channelToNpmTag", () => {
|
||||||
|
|
@ -42,9 +47,12 @@ describe("channelToNpmTag", () => {
|
||||||
{ channel: "stable", expected: "latest" },
|
{ channel: "stable", expected: "latest" },
|
||||||
{ channel: "beta", expected: "beta" },
|
{ channel: "beta", expected: "beta" },
|
||||||
{ channel: "dev", expected: "dev" },
|
{ channel: "dev", expected: "dev" },
|
||||||
])("maps $channel to $expected", ({ channel, expected }) => {
|
] satisfies Array<{ channel: UpdateChannel; expected: string }>)(
|
||||||
expect(channelToNpmTag(channel)).toBe(expected);
|
"maps $channel to $expected",
|
||||||
});
|
({ channel, expected }) => {
|
||||||
|
expect(channelToNpmTag(channel)).toBe(expected);
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("resolveEffectiveUpdateChannel", () => {
|
describe("resolveEffectiveUpdateChannel", () => {
|
||||||
|
|
@ -100,7 +108,11 @@ describe("resolveEffectiveUpdateChannel", () => {
|
||||||
params: { installKind: "unknown" as const },
|
params: { installKind: "unknown" as const },
|
||||||
expected: { channel: "stable", source: "default" },
|
expected: { channel: "stable", source: "default" },
|
||||||
},
|
},
|
||||||
])("$name", ({ params, expected }) => {
|
] satisfies Array<{
|
||||||
|
name: string;
|
||||||
|
params: Parameters<typeof resolveEffectiveUpdateChannel>[0];
|
||||||
|
expected: { channel: UpdateChannel; source: UpdateChannelSource };
|
||||||
|
}>)("$name", ({ params, expected }) => {
|
||||||
expect(resolveEffectiveUpdateChannel(params)).toEqual(expected);
|
expect(resolveEffectiveUpdateChannel(params)).toEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -145,7 +157,11 @@ describe("formatUpdateChannelLabel", () => {
|
||||||
params: { channel: "stable", source: "default" as const },
|
params: { channel: "stable", source: "default" as const },
|
||||||
expected: "stable (default)",
|
expected: "stable (default)",
|
||||||
},
|
},
|
||||||
])("$name", ({ params, expected }) => {
|
] satisfies Array<{
|
||||||
|
name: string;
|
||||||
|
params: Parameters<typeof formatUpdateChannelLabel>[0];
|
||||||
|
expected: string;
|
||||||
|
}>)("$name", ({ params, expected }) => {
|
||||||
expect(formatUpdateChannelLabel(params)).toBe(expected);
|
expect(formatUpdateChannelLabel(params)).toBe(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,20 @@ import {
|
||||||
normalizeWideAreaDomain,
|
normalizeWideAreaDomain,
|
||||||
renderWideAreaGatewayZoneText,
|
renderWideAreaGatewayZoneText,
|
||||||
resolveWideAreaDiscoveryDomain,
|
resolveWideAreaDiscoveryDomain,
|
||||||
|
type WideAreaGatewayZoneOpts,
|
||||||
writeWideAreaGatewayZone,
|
writeWideAreaGatewayZone,
|
||||||
} from "./widearea-dns.js";
|
} from "./widearea-dns.js";
|
||||||
|
|
||||||
const baseZoneOpts = {
|
const baseZoneOpts: WideAreaGatewayZoneOpts = {
|
||||||
domain: "openclaw.internal.",
|
domain: "openclaw.internal.",
|
||||||
gatewayPort: 18789,
|
gatewayPort: 18789,
|
||||||
displayName: "Mac Studio (OpenClaw)",
|
displayName: "Mac Studio (OpenClaw)",
|
||||||
tailnetIPv4: "100.123.224.76",
|
tailnetIPv4: "100.123.224.76",
|
||||||
hostLabel: "studio-london",
|
hostLabel: "studio-london",
|
||||||
instanceLabel: "studio-london",
|
instanceLabel: "studio-london",
|
||||||
} as const;
|
};
|
||||||
|
|
||||||
function makeZoneOpts(overrides: Partial<typeof baseZoneOpts> = {}) {
|
function makeZoneOpts(overrides: Partial<WideAreaGatewayZoneOpts> = {}): WideAreaGatewayZoneOpts {
|
||||||
return { ...baseZoneOpts, ...overrides };
|
return { ...baseZoneOpts, ...overrides };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -678,7 +678,7 @@ describe("handleLineWebhookEvents", () => {
|
||||||
it("skips group messages by default when requireMention is not configured", async () => {
|
it("skips group messages by default when requireMention is not configured", async () => {
|
||||||
const processMessage = vi.fn();
|
const processMessage = vi.fn();
|
||||||
const event = createTestMessageEvent({
|
const event = createTestMessageEvent({
|
||||||
message: { id: "m-default-skip", type: "text", text: "hi there" },
|
message: { id: "m-default-skip", type: "text", text: "hi there", quoteToken: "q-default" },
|
||||||
source: { type: "group", groupId: "group-default", userId: "user-default" },
|
source: { type: "group", groupId: "group-default", userId: "user-default" },
|
||||||
webhookEventId: "evt-default-skip",
|
webhookEventId: "evt-default-skip",
|
||||||
});
|
});
|
||||||
|
|
@ -702,7 +702,7 @@ describe("handleLineWebhookEvents", () => {
|
||||||
import("../auto-reply/reply/history.js").HistoryEntry[]
|
import("../auto-reply/reply/history.js").HistoryEntry[]
|
||||||
>();
|
>();
|
||||||
const event = createTestMessageEvent({
|
const event = createTestMessageEvent({
|
||||||
message: { id: "m-hist-1", type: "text", text: "hello history" },
|
message: { id: "m-hist-1", type: "text", text: "hello history", quoteToken: "q-hist-1" },
|
||||||
timestamp: 1700000000000,
|
timestamp: 1700000000000,
|
||||||
source: { type: "group", groupId: "group-hist-1", userId: "user-hist" },
|
source: { type: "group", groupId: "group-hist-1", userId: "user-hist" },
|
||||||
webhookEventId: "evt-hist-1",
|
webhookEventId: "evt-hist-1",
|
||||||
|
|
@ -730,7 +730,7 @@ describe("handleLineWebhookEvents", () => {
|
||||||
it("skips group messages without mention when requireMention is set", async () => {
|
it("skips group messages without mention when requireMention is set", async () => {
|
||||||
const processMessage = vi.fn();
|
const processMessage = vi.fn();
|
||||||
const event = createTestMessageEvent({
|
const event = createTestMessageEvent({
|
||||||
message: { id: "m-mention-1", type: "text", text: "hi there" },
|
message: { id: "m-mention-1", type: "text", text: "hi there", quoteToken: "q-mention-1" },
|
||||||
source: { type: "group", groupId: "group-mention", userId: "user-mention" },
|
source: { type: "group", groupId: "group-mention", userId: "user-mention" },
|
||||||
webhookEventId: "evt-mention-1",
|
webhookEventId: "evt-mention-1",
|
||||||
});
|
});
|
||||||
|
|
@ -808,7 +808,7 @@ describe("handleLineWebhookEvents", () => {
|
||||||
it("does not apply requireMention gating to DM messages", async () => {
|
it("does not apply requireMention gating to DM messages", async () => {
|
||||||
const processMessage = vi.fn();
|
const processMessage = vi.fn();
|
||||||
const event = createTestMessageEvent({
|
const event = createTestMessageEvent({
|
||||||
message: { id: "m-mention-dm", type: "text", text: "hi" },
|
message: { id: "m-mention-dm", type: "text", text: "hi", quoteToken: "q-mention-dm" },
|
||||||
source: { type: "user", userId: "user-dm" },
|
source: { type: "user", userId: "user-dm" },
|
||||||
webhookEventId: "evt-mention-dm",
|
webhookEventId: "evt-mention-dm",
|
||||||
});
|
});
|
||||||
|
|
@ -830,7 +830,12 @@ describe("handleLineWebhookEvents", () => {
|
||||||
const processMessage = vi.fn();
|
const processMessage = vi.fn();
|
||||||
// Image message -- LINE only carries mention metadata on text messages.
|
// Image message -- LINE only carries mention metadata on text messages.
|
||||||
const event = createTestMessageEvent({
|
const event = createTestMessageEvent({
|
||||||
message: { id: "m-mention-img", type: "image", contentProvider: { type: "line" } },
|
message: {
|
||||||
|
id: "m-mention-img",
|
||||||
|
type: "image",
|
||||||
|
contentProvider: { type: "line" },
|
||||||
|
quoteToken: "q-mention-img",
|
||||||
|
},
|
||||||
source: { type: "group", groupId: "group-1", userId: "user-img" },
|
source: { type: "group", groupId: "group-1", userId: "user-img" },
|
||||||
webhookEventId: "evt-mention-img",
|
webhookEventId: "evt-mention-img",
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1488,7 +1488,7 @@ describe("loadOpenClawPlugins", () => {
|
||||||
load: { paths: [plugin.file] },
|
load: { paths: [plugin.file] },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
};
|
||||||
|
|
||||||
loadOpenClawPlugins(options);
|
loadOpenClawPlugins(options);
|
||||||
loadOpenClawPlugins(options);
|
loadOpenClawPlugins(options);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import type { TelegramNetworkConfig } from "../config/types.telegram.js";
|
||||||
import {
|
import {
|
||||||
resetTelegramNetworkConfigStateForTests,
|
resetTelegramNetworkConfigStateForTests,
|
||||||
resolveTelegramAutoSelectFamilyDecision,
|
resolveTelegramAutoSelectFamilyDecision,
|
||||||
|
|
@ -157,7 +158,9 @@ describe("resolveTelegramDnsResultOrderDecision", () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "normalizes trimmed config values",
|
name: "normalizes trimmed config values",
|
||||||
network: { dnsResultOrder: " Verbatim " },
|
network: { dnsResultOrder: " Verbatim " } as TelegramNetworkConfig & {
|
||||||
|
dnsResultOrder: string;
|
||||||
|
},
|
||||||
nodeMajor: 20,
|
nodeMajor: 20,
|
||||||
expected: { value: "verbatim", source: "config" },
|
expected: { value: "verbatim", source: "config" },
|
||||||
},
|
},
|
||||||
|
|
@ -171,11 +174,17 @@ describe("resolveTelegramDnsResultOrderDecision", () => {
|
||||||
{
|
{
|
||||||
name: "ignores invalid env and config values before applying Node 22 default",
|
name: "ignores invalid env and config values before applying Node 22 default",
|
||||||
env: { OPENCLAW_TELEGRAM_DNS_RESULT_ORDER: "bogus" },
|
env: { OPENCLAW_TELEGRAM_DNS_RESULT_ORDER: "bogus" },
|
||||||
network: { dnsResultOrder: "invalid" },
|
network: { dnsResultOrder: "invalid" } as TelegramNetworkConfig & { dnsResultOrder: string },
|
||||||
nodeMajor: 22,
|
nodeMajor: 22,
|
||||||
expected: { value: "ipv4first", source: "default-node22" },
|
expected: { value: "ipv4first", source: "default-node22" },
|
||||||
},
|
},
|
||||||
])("$name", ({ env, network, nodeMajor, expected }) => {
|
] satisfies Array<{
|
||||||
|
name: string;
|
||||||
|
env?: NodeJS.ProcessEnv;
|
||||||
|
network?: TelegramNetworkConfig | (TelegramNetworkConfig & { dnsResultOrder: string });
|
||||||
|
nodeMajor: number;
|
||||||
|
expected: ReturnType<typeof resolveTelegramDnsResultOrderDecision>;
|
||||||
|
}>)("$name", ({ env, network, nodeMajor, expected }) => {
|
||||||
const decision = resolveTelegramDnsResultOrderDecision({
|
const decision = resolveTelegramDnsResultOrderDecision({
|
||||||
env,
|
env,
|
||||||
network,
|
network,
|
||||||
|
|
|
||||||
|
|
@ -28,14 +28,7 @@ export function expectSingleNpmInstallIgnoreScriptsCall(params: {
|
||||||
throw new Error("expected npm install call");
|
throw new Error("expected npm install call");
|
||||||
}
|
}
|
||||||
const [argv, opts] = first;
|
const [argv, opts] = first;
|
||||||
expect(argv).toEqual([
|
expect(argv).toEqual(["npm", "install", "--omit=dev", "--silent", "--ignore-scripts"]);
|
||||||
"npm",
|
|
||||||
"install",
|
|
||||||
"--omit=dev",
|
|
||||||
"--omit=peer",
|
|
||||||
"--silent",
|
|
||||||
"--ignore-scripts",
|
|
||||||
]);
|
|
||||||
expect(opts?.cwd).toBeTruthy();
|
expect(opts?.cwd).toBeTruthy();
|
||||||
const cwd = String(opts?.cwd);
|
const cwd = String(opts?.cwd);
|
||||||
const expectedTargetDir = params.expectedTargetDir;
|
const expectedTargetDir = params.expectedTargetDir;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue