chore(tsgo/lint): fix CI errors

This commit is contained in:
Gustavo Madeira Santana
2026-03-02 03:03:11 -05:00
parent 22be0c5801
commit 1443bb9a84
17 changed files with 132 additions and 103 deletions

View File

@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../config/config.js"; import type { OpenClawConfig } from "../config/config.js";
import type { ModelProviderConfig } from "../config/types.models.js";
import { validateConfigObject } from "../config/validation.js"; import { validateConfigObject } from "../config/validation.js";
import { resolveOpenClawAgentDir } from "./agent-paths.js"; import { resolveOpenClawAgentDir } from "./agent-paths.js";
import { import {
@@ -41,7 +42,7 @@ async function writeAgentModelsJson(content: unknown): Promise<void> {
} }
function createMergeConfigProvider() { function createMergeConfigProvider() {
return { const provider: ModelProviderConfig = {
baseUrl: "https://config.example/v1", baseUrl: "https://config.example/v1",
apiKey: "CONFIG_KEY", apiKey: "CONFIG_KEY",
api: "openai-responses", api: "openai-responses",
@@ -56,7 +57,8 @@ function createMergeConfigProvider() {
maxTokens: 2048, maxTokens: 2048,
}, },
], ],
} as const; };
return provider;
} }
async function runCustomProviderMergeTest(seedProvider: { async function runCustomProviderMergeTest(seedProvider: {

View File

@@ -11,7 +11,6 @@ function applyThinkingDefault(thinking: ThinkingLevel) {
harness.setSessionsSpawnConfigOverride({ harness.setSessionsSpawnConfigOverride({
session: { mainKey: "main", scope: "per-sender" }, session: { mainKey: "main", scope: "per-sender" },
agents: { defaults: { subagents: { thinking } } }, agents: { defaults: { subagents: { thinking } } },
routing: { sessions: { mainKey: MAIN_SESSION_KEY } },
}); });
} }

View File

@@ -15,7 +15,6 @@ function configureDefaultsWithoutTimeout() {
setSessionsSpawnConfigOverride({ setSessionsSpawnConfigOverride({
session: { mainKey: "main", scope: "per-sender" }, session: { mainKey: "main", scope: "per-sender" },
agents: { defaults: { subagents: { maxConcurrent: 8 } } }, agents: { defaults: { subagents: { maxConcurrent: 8 } } },
routing: { sessions: { mainKey: MAIN_SESSION_KEY } },
}); });
} }

View File

@@ -9,7 +9,6 @@ function applySubagentTimeoutDefault(seconds: number) {
sessionsHarness.setSessionsSpawnConfigOverride({ sessionsHarness.setSessionsSpawnConfigOverride({
session: { mainKey: "main", scope: "per-sender" }, session: { mainKey: "main", scope: "per-sender" },
agents: { defaults: { subagents: { runTimeoutSeconds: seconds } } }, agents: { defaults: { subagents: { runTimeoutSeconds: seconds } } },
routing: { sessions: { mainKey: MAIN_SESSION_KEY } },
}); });
} }

View File

@@ -1,4 +1,4 @@
import { vi } from "vitest"; import { vi, type Mock } from "vitest";
type SessionsSpawnTestConfig = ReturnType<(typeof import("../config/config.js"))["loadConfig"]>; type SessionsSpawnTestConfig = ReturnType<(typeof import("../config/config.js"))["loadConfig"]>;
type CreateSessionsSpawnTool = type CreateSessionsSpawnTool =
@@ -16,10 +16,6 @@ type SessionsSpawnGatewayMockOptions = {
agentWaitResult?: { status: "ok" | "timeout"; startedAt: number; endedAt: number }; agentWaitResult?: { status: "ok" | "timeout"; startedAt: number; endedAt: number };
}; };
// Avoid exporting vitest mock types (TS2742 under pnpm + d.ts emit).
// oxlint-disable-next-line typescript/no-explicit-any
type AnyMock = any;
const hoisted = vi.hoisted(() => { const hoisted = vi.hoisted(() => {
const callGatewayMock = vi.fn(); const callGatewayMock = vi.fn();
const defaultConfigOverride = { const defaultConfigOverride = {
@@ -32,12 +28,12 @@ const hoisted = vi.hoisted(() => {
return { callGatewayMock, defaultConfigOverride, state }; return { callGatewayMock, defaultConfigOverride, state };
}); });
export function getCallGatewayMock(): AnyMock { export function getCallGatewayMock(): Mock {
return hoisted.callGatewayMock; return hoisted.callGatewayMock;
} }
export function getGatewayRequests(): Array<GatewayRequest> { export function getGatewayRequests(): Array<GatewayRequest> {
return getCallGatewayMock().mock.calls.map((call: [unknown]) => call[0] as GatewayRequest); return getCallGatewayMock().mock.calls.map((call: unknown[]) => call[0] as GatewayRequest);
} }
export function getGatewayMethods(): Array<string | undefined> { export function getGatewayMethods(): Array<string | undefined> {

View File

@@ -16,15 +16,15 @@ async function withFakeTimers(run: () => Promise<void>) {
} }
function createTypingHarness(overrides: Partial<Parameters<typeof createTypingCallbacks>[0]> = {}) { function createTypingHarness(overrides: Partial<Parameters<typeof createTypingCallbacks>[0]> = {}) {
const start = overrides.start ?? vi.fn().mockResolvedValue(undefined); const start = vi.fn(overrides.start ?? (async () => {}));
const stop = overrides.stop ?? vi.fn().mockResolvedValue(undefined); const stop = vi.fn(overrides.stop ?? (async () => {}));
const onStartError = overrides.onStartError ?? vi.fn(); const onStartError = vi.fn(overrides.onStartError ?? (() => {}));
const onStopError = overrides.onStopError ?? vi.fn(); const onStopError = vi.fn(overrides.onStopError ?? (() => {}));
const callbacks = createTypingCallbacks({ const callbacks = createTypingCallbacks({
start, start,
stop, stop,
onStartError, onStartError,
...(onStopError ? { onStopError } : {}), onStopError,
...(overrides.maxConsecutiveFailures !== undefined ...(overrides.maxConsecutiveFailures !== undefined
? { maxConsecutiveFailures: overrides.maxConsecutiveFailures } ? { maxConsecutiveFailures: overrides.maxConsecutiveFailures }
: {}), : {}),

View File

@@ -227,7 +227,7 @@ function createTelegramOutboundPlugin() {
}; };
to: string; to: string;
text: string; text: string;
accountId?: string; accountId?: string | null;
mediaUrl?: string; mediaUrl?: string;
}, },
mediaUrl?: string, mediaUrl?: string,

View File

@@ -22,23 +22,25 @@ export function setDefaultChannelPluginRegistryForTests(): void {
setActivePluginRegistry(createTestRegistry(channels)); setActivePluginRegistry(createTestRegistry(channels));
} }
export function patchChannelOnboardingAdapter<K extends keyof ChannelOnboardingAdapter>( export function patchChannelOnboardingAdapter(
channel: ChannelChoice, channel: ChannelChoice,
patch: Pick<ChannelOnboardingAdapter, K>, patch: Partial<ChannelOnboardingAdapter>,
): () => void { ): () => void {
const adapter = getChannelOnboardingAdapter(channel); const adapter = getChannelOnboardingAdapter(channel);
if (!adapter) { if (!adapter) {
throw new Error(`missing onboarding adapter for ${channel}`); throw new Error(`missing onboarding adapter for ${channel}`);
} }
const keys = Object.keys(patch) as K[]; const keys = Object.keys(patch);
const previous = {} as Pick<ChannelOnboardingAdapter, K>; const adapterRecord = adapter as unknown as Record<string, unknown>;
const patchRecord = patch as Record<string, unknown>;
const previous = new Map<string, unknown>();
for (const key of keys) { for (const key of keys) {
previous[key] = adapter[key]; previous.set(key, adapterRecord[key]);
adapter[key] = patch[key]; adapterRecord[key] = patchRecord[key];
} }
return () => { return () => {
for (const key of keys) { for (const key of keys) {
adapter[key] = previous[key]; adapterRecord[key] = previous.get(key);
} }
}; };
} }

View File

@@ -1,4 +1,5 @@
import { beforeEach, describe, expect, it, vi } from "vitest"; import { beforeEach, describe, expect, it, vi } from "vitest";
import type { ChannelOnboardingAdapter } from "../channels/plugins/onboarding-types.js";
import type { OpenClawConfig } from "../config/config.js"; import type { OpenClawConfig } from "../config/config.js";
import { createEmptyPluginRegistry } from "../plugins/registry.js"; import { createEmptyPluginRegistry } from "../plugins/registry.js";
import { setActivePluginRegistry } from "../plugins/runtime.js"; import { setActivePluginRegistry } from "../plugins/runtime.js";
@@ -82,14 +83,17 @@ function createTelegramCfg(botToken: string, enabled?: boolean): OpenClawConfig
} as OpenClawConfig; } as OpenClawConfig;
} }
function patchTelegramAdapter(overrides: Parameters<typeof patchChannelOnboardingAdapter>[1]) { function patchTelegramAdapter(overrides: Partial<ChannelOnboardingAdapter>) {
return patchChannelOnboardingAdapter("telegram", { const getStatus =
getStatus: vi.fn(async ({ cfg }: { cfg: OpenClawConfig }) => ({ overrides.getStatus ??
vi.fn(async ({ cfg }: { cfg: OpenClawConfig }) => ({
channel: "telegram", channel: "telegram",
configured: Boolean(cfg.channels?.telegram?.botToken), configured: Boolean(cfg.channels?.telegram?.botToken),
statusLines: [], statusLines: [],
})), }));
return patchChannelOnboardingAdapter("telegram", {
...overrides, ...overrides,
getStatus,
}); });
} }

View File

@@ -1,5 +1,7 @@
import { afterEach, describe, expect, it, vi } from "vitest"; import { afterEach, describe, expect, it, vi } from "vitest";
import { CONTEXT_WINDOW_HARD_MIN_TOKENS } from "../agents/context-window-guard.js"; import { CONTEXT_WINDOW_HARD_MIN_TOKENS } from "../agents/context-window-guard.js";
import type { OpenClawConfig } from "../config/config.js";
import type { ModelProviderConfig } from "../config/types.models.js";
import { defaultRuntime } from "../runtime.js"; import { defaultRuntime } from "../runtime.js";
import { import {
applyCustomApiConfig, applyCustomApiConfig,
@@ -76,28 +78,29 @@ function expectOpenAiCompatResult(params: {
expect(params.result.config.models?.providers?.custom?.api).toBe("openai-completions"); expect(params.result.config.models?.providers?.custom?.api).toBe("openai-completions");
} }
function buildCustomProviderConfig(contextWindow?: number) { function buildCustomProviderConfig(contextWindow?: number): OpenClawConfig {
if (contextWindow === undefined) { if (contextWindow === undefined) {
return {}; return {};
} }
const customProvider = {
api: "openai-completions",
baseUrl: "https://llm.example.com/v1",
models: [
{
id: "foo-large",
name: "foo-large",
contextWindow,
maxTokens: contextWindow > CONTEXT_WINDOW_HARD_MIN_TOKENS ? 4096 : 1024,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
reasoning: false,
},
],
} satisfies ModelProviderConfig;
return { return {
models: { models: {
providers: { providers: {
custom: { custom: customProvider,
api: "openai-completions",
baseUrl: "https://llm.example.com/v1",
models: [
{
id: "foo-large",
name: "foo-large",
contextWindow,
maxTokens: contextWindow > CONTEXT_WINDOW_HARD_MIN_TOKENS ? 4096 : 1024,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
reasoning: false,
},
],
},
}, },
}, },
}; };

View File

@@ -1,10 +1,12 @@
import { vi } from "vitest"; import { vi, type Mock } from "vitest";
type CronSessionEntry = { type CronSessionEntry = {
sessionId: string; sessionId: string;
updatedAt: number; updatedAt: number;
systemSent: boolean; systemSent: boolean;
skillsSnapshot: unknown; skillsSnapshot: unknown;
model?: string;
modelProvider?: string;
[key: string]: unknown; [key: string]: unknown;
}; };
@@ -17,23 +19,27 @@ type CronSession = {
[key: string]: unknown; [key: string]: unknown;
}; };
export const buildWorkspaceSkillSnapshotMock = vi.fn(); function createMock(): Mock {
export const resolveAgentConfigMock = vi.fn(); return vi.fn();
export const resolveAgentModelFallbacksOverrideMock = vi.fn(); }
export const resolveAgentSkillsFilterMock = vi.fn();
export const getModelRefStatusMock = vi.fn(); export const buildWorkspaceSkillSnapshotMock = createMock();
export const isCliProviderMock = vi.fn(); export const resolveAgentConfigMock = createMock();
export const resolveAllowedModelRefMock = vi.fn(); export const resolveAgentModelFallbacksOverrideMock = createMock();
export const resolveConfiguredModelRefMock = vi.fn(); export const resolveAgentSkillsFilterMock = createMock();
export const resolveHooksGmailModelMock = vi.fn(); export const getModelRefStatusMock = createMock();
export const resolveThinkingDefaultMock = vi.fn(); export const isCliProviderMock = createMock();
export const runWithModelFallbackMock = vi.fn(); export const resolveAllowedModelRefMock = createMock();
export const runEmbeddedPiAgentMock = vi.fn(); export const resolveConfiguredModelRefMock = createMock();
export const runCliAgentMock = vi.fn(); export const resolveHooksGmailModelMock = createMock();
export const getCliSessionIdMock = vi.fn(); export const resolveThinkingDefaultMock = createMock();
export const updateSessionStoreMock = vi.fn(); export const runWithModelFallbackMock = createMock();
export const resolveCronSessionMock = vi.fn(); export const runEmbeddedPiAgentMock = createMock();
export const logWarnMock = vi.fn(); export const runCliAgentMock = createMock();
export const getCliSessionIdMock = createMock();
export const updateSessionStoreMock = createMock();
export const resolveCronSessionMock = createMock();
export const logWarnMock = createMock();
vi.mock("../../agents/agent-scope.js", () => ({ vi.mock("../../agents/agent-scope.js", () => ({
resolveAgentConfig: resolveAgentConfigMock, resolveAgentConfig: resolveAgentConfigMock,

View File

@@ -4,7 +4,11 @@ import path from "node:path";
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import { saveExecApprovals } from "../infra/exec-approvals.js"; import { saveExecApprovals } from "../infra/exec-approvals.js";
import type { ExecHostResponse } from "../infra/exec-host.js"; import type { ExecHostResponse } from "../infra/exec-host.js";
import { handleSystemRunInvoke, formatSystemRunAllowlistMissMessage } from "./invoke-system-run.js"; import {
handleSystemRunInvoke,
formatSystemRunAllowlistMissMessage,
type HandleSystemRunInvokeOptions,
} from "./invoke-system-run.js";
describe("formatSystemRunAllowlistMissMessage", () => { describe("formatSystemRunAllowlistMissMessage", () => {
it("returns legacy allowlist miss message by default", () => { it("returns legacy allowlist miss message by default", () => {
@@ -181,12 +185,14 @@ describe("handleSystemRunInvoke mac app exec host routing", () => {
resolveExecAsk: () => params.ask ?? "off", resolveExecAsk: () => params.ask ?? "off",
isCmdExeInvocation: () => false, isCmdExeInvocation: () => false,
sanitizeEnv: () => undefined, sanitizeEnv: () => undefined,
runCommand, runCommand: runCommand as HandleSystemRunInvokeOptions["runCommand"],
runViaMacAppExecHost, runViaMacAppExecHost:
sendNodeEvent, runViaMacAppExecHost as HandleSystemRunInvokeOptions["runViaMacAppExecHost"],
sendNodeEvent: sendNodeEvent as HandleSystemRunInvokeOptions["sendNodeEvent"],
buildExecEventPayload: (payload) => payload, buildExecEventPayload: (payload) => payload,
sendInvokeResult, sendInvokeResult: sendInvokeResult as HandleSystemRunInvokeOptions["sendInvokeResult"],
sendExecFinishedEvent, sendExecFinishedEvent:
sendExecFinishedEvent as HandleSystemRunInvokeOptions["sendExecFinishedEvent"],
preferMacAppExecHost: params.preferMacAppExecHost, preferMacAppExecHost: params.preferMacAppExecHost,
}); });

View File

@@ -3,6 +3,7 @@ import os from "node:os";
import path from "node:path"; import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest"; import { afterEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js"; import type { OpenClawConfig } from "../config/config.js";
import type { ExecSecretProviderConfig, SecretProviderConfig } from "../config/types.secrets.js";
import { resolveSecretRefString, resolveSecretRefValue } from "./resolve.js"; import { resolveSecretRefString, resolveSecretRefValue } from "./resolve.js";
async function writeSecureFile(filePath: string, content: string, mode = 0o600): Promise<void> { async function writeSecureFile(filePath: string, content: string, mode = 0o600): Promise<void> {
@@ -28,7 +29,7 @@ describe("secret ref resolver", () => {
function createProviderConfig( function createProviderConfig(
providerId: string, providerId: string,
provider: Record<string, unknown>, provider: SecretProviderConfig,
): OpenClawConfig { ): OpenClawConfig {
return { return {
secrets: { secrets: {
@@ -42,7 +43,7 @@ describe("secret ref resolver", () => {
async function resolveWithProvider(params: { async function resolveWithProvider(params: {
ref: Parameters<typeof resolveSecretRefString>[0]; ref: Parameters<typeof resolveSecretRefString>[0];
providerId: string; providerId: string;
provider: Record<string, unknown>; provider: SecretProviderConfig;
}) { }) {
return await resolveSecretRefString(params.ref, { return await resolveSecretRefString(params.ref, {
config: createProviderConfig(params.providerId, params.provider), config: createProviderConfig(params.providerId, params.provider),
@@ -51,8 +52,8 @@ describe("secret ref resolver", () => {
function createExecProvider( function createExecProvider(
command: string, command: string,
overrides?: Record<string, unknown>, overrides?: Partial<ExecSecretProviderConfig>,
): Record<string, unknown> { ): ExecSecretProviderConfig {
return { return {
source: "exec", source: "exec",
command, command,
@@ -62,7 +63,7 @@ describe("secret ref resolver", () => {
} }
async function expectExecResolveRejects( async function expectExecResolveRejects(
provider: Record<string, unknown>, provider: SecretProviderConfig,
message: string, message: string,
): Promise<void> { ): Promise<void> {
await expect( await expect(

View File

@@ -72,7 +72,7 @@ async function runMemberCase(args: MemberCaseArgs = {}): Promise<void> {
} }
describe("registerSlackMemberEvents", () => { describe("registerSlackMemberEvents", () => {
it.each([ const cases: Array<{ name: string; args: MemberCaseArgs; calls: number }> = [
{ {
name: "enqueues DM member events when dmPolicy is open", name: "enqueues DM member events when dmPolicy is open",
args: { overrides: { dmPolicy: "open" } }, args: { overrides: { dmPolicy: "open" } },
@@ -112,7 +112,9 @@ describe("registerSlackMemberEvents", () => {
}, },
calls: 0, calls: 0,
}, },
])("$name", async ({ args, calls }) => { ];
it.each(cases)("$name", async ({ args, calls }) => {
await runMemberCase(args); await runMemberCase(args);
expect(memberMocks.enqueue).toHaveBeenCalledTimes(calls); expect(memberMocks.enqueue).toHaveBeenCalledTimes(calls);
}); });

View File

@@ -87,7 +87,7 @@ async function runMessageCase(input: MessageCase = {}): Promise<void> {
} }
describe("registerSlackMessageEvents", () => { describe("registerSlackMessageEvents", () => {
it.each([ const cases: Array<{ name: string; input: MessageCase; calls: number }> = [
{ {
name: "enqueues message_changed system events when dmPolicy is open", name: "enqueues message_changed system events when dmPolicy is open",
input: { overrides: { dmPolicy: "open" }, event: makeChangedEvent() }, input: { overrides: { dmPolicy: "open" }, event: makeChangedEvent() },
@@ -130,7 +130,9 @@ describe("registerSlackMessageEvents", () => {
}, },
calls: 0, calls: 0,
}, },
])("$name", async ({ input, calls }) => { ];
it.each(cases)("$name", async ({ input, calls }) => {
await runMessageCase(input); await runMessageCase(input);
expect(messageQueueMock).toHaveBeenCalledTimes(calls); expect(messageQueueMock).toHaveBeenCalledTimes(calls);
}); });

View File

@@ -75,32 +75,36 @@ async function runPinCase(input: PinCase = {}): Promise<void> {
} }
describe("registerSlackPinEvents", () => { describe("registerSlackPinEvents", () => {
it.each([ const cases: Array<{ name: string; args: PinCase; expectedCalls: number }> = [
["enqueues DM pin system events when dmPolicy is open", { overrides: { dmPolicy: "open" } }, 1], {
[ name: "enqueues DM pin system events when dmPolicy is open",
"blocks DM pin system events when dmPolicy is disabled", args: { overrides: { dmPolicy: "open" } },
{ overrides: { dmPolicy: "disabled" } }, expectedCalls: 1,
0, },
], {
[ name: "blocks DM pin system events when dmPolicy is disabled",
"blocks DM pin system events for unauthorized senders in allowlist mode", args: { overrides: { dmPolicy: "disabled" } },
{ expectedCalls: 0,
},
{
name: "blocks DM pin system events for unauthorized senders in allowlist mode",
args: {
overrides: { dmPolicy: "allowlist", allowFrom: ["U2"] }, overrides: { dmPolicy: "allowlist", allowFrom: ["U2"] },
event: makePinEvent({ user: "U1" }), event: makePinEvent({ user: "U1" }),
}, },
0, expectedCalls: 0,
], },
[ {
"allows DM pin system events for authorized senders in allowlist mode", name: "allows DM pin system events for authorized senders in allowlist mode",
{ args: {
overrides: { dmPolicy: "allowlist", allowFrom: ["U1"] }, overrides: { dmPolicy: "allowlist", allowFrom: ["U1"] },
event: makePinEvent({ user: "U1" }), event: makePinEvent({ user: "U1" }),
}, },
1, expectedCalls: 1,
], },
[ {
"blocks channel pin events for users outside channel users allowlist", name: "blocks channel pin events for users outside channel users allowlist",
{ args: {
overrides: { overrides: {
dmPolicy: "open", dmPolicy: "open",
channelType: "channel", channelType: "channel",
@@ -108,9 +112,11 @@ describe("registerSlackPinEvents", () => {
}, },
event: makePinEvent({ channel: "C1", user: "U_ATTACKER" }), event: makePinEvent({ channel: "C1", user: "U_ATTACKER" }),
}, },
0, expectedCalls: 0,
], },
])("%s", async (_name, args: PinCase, expectedCalls: number) => { ];
it.each(cases)("$name", async ({ args, expectedCalls }) => {
await runPinCase(args); await runPinCase(args);
expect(pinEnqueueMock).toHaveBeenCalledTimes(expectedCalls); expect(pinEnqueueMock).toHaveBeenCalledTimes(expectedCalls);
}); });

View File

@@ -78,7 +78,7 @@ async function executeReactionCase(input: ReactionRunInput = {}) {
} }
describe("registerSlackReactionEvents", () => { describe("registerSlackReactionEvents", () => {
it.each([ const cases: Array<{ name: string; args: ReactionRunInput; expectedCalls: number }> = [
{ {
name: "enqueues DM reaction system events when dmPolicy is open", name: "enqueues DM reaction system events when dmPolicy is open",
args: { overrides: { dmPolicy: "open" } }, args: { overrides: { dmPolicy: "open" } },
@@ -129,7 +129,9 @@ describe("registerSlackReactionEvents", () => {
}, },
expectedCalls: 0, expectedCalls: 0,
}, },
])("$name", async ({ args, expectedCalls }) => { ];
it.each(cases)("$name", async ({ args, expectedCalls }) => {
await executeReactionCase(args); await executeReactionCase(args);
expect(reactionQueueMock).toHaveBeenCalledTimes(expectedCalls); expect(reactionQueueMock).toHaveBeenCalledTimes(expectedCalls);
}); });