chore: enable no-base-to-string

This commit is contained in:
Peter Steinberger
2026-04-10 20:08:00 +01:00
parent dfe4c2d16d
commit 2786ed0f67
22 changed files with 92 additions and 35 deletions

View File

@@ -59,7 +59,6 @@
"**/*test-support.ts"
],
"rules": {
"typescript/no-base-to-string": "off",
"typescript/no-explicit-any": "off",
"typescript/no-redundant-type-constituents": "off",
"typescript/unbound-method": "off",

View File

@@ -364,7 +364,10 @@ describe("broadcast dispatch", () => {
});
expect(mockDispatchReplyFromConfig).toHaveBeenCalledTimes(1);
const sessionKey = String(finalizeInboundContextCalls[0]?.SessionKey ?? "");
const sessionKey =
typeof finalizeInboundContextCalls[0]?.SessionKey === "string"
? finalizeInboundContextCalls[0].SessionKey
: "";
expect(sessionKey).toBe("agent:susan:feishu:group:oc-broadcast-group");
});
});

View File

@@ -539,7 +539,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
expect(streamingInstances).toHaveLength(1);
const updateCalls = streamingInstances[0].update.mock.calls.map((c: unknown[]) =>
String(c[0] ?? ""),
typeof c[0] === "string" ? c[0] : "",
);
const reasoningUpdate = updateCalls.find((c) => c.includes("Thinking"));
expect(reasoningUpdate).toContain("> 💭 **Thinking**");

View File

@@ -180,7 +180,7 @@ describe("feishu tool account routing", () => {
const result = await tool.execute("call", { action: "search" });
expect(createFeishuClientMock).not.toHaveBeenCalled();
expect(String(result.details.error ?? "")).toContain(
expect(typeof result.details.error === "string" ? result.details.error : "").toContain(
"Resolve this command against an active gateway runtime snapshot before reading it.",
);
});

View File

@@ -706,7 +706,9 @@ describe("loginGeminiCliOAuth", () => {
expect(clientMetadata).toBeDefined();
expect(JSON.parse(clientMetadata as string)).toEqual(EXPECTED_LOAD_CODE_ASSIST_METADATA);
const body = JSON.parse(String(loadRequests[0]?.init?.body));
const loadBody = loadRequests[0]?.init?.body;
expect(typeof loadBody).toBe("string");
const body = JSON.parse(loadBody as string);
expect(body).toEqual({
metadata: EXPECTED_LOAD_CODE_ASSIST_METADATA,
});

View File

@@ -180,7 +180,7 @@ describe("matrix monitor handler pairing account scope", () => {
await handler("!room:example.org", makeEvent("$event1"));
await handler("!room:example.org", makeEvent("$event2"));
expect(sendMessageMatrixMock).toHaveBeenCalledTimes(1);
expect(String(sendMessageMatrixMock.mock.calls[0]?.[1] ?? "")).toContain(
expect(sendMessageMatrixMock.mock.calls[0]?.[1]).toContain(
"Pairing request is still pending approval.",
);

View File

@@ -257,14 +257,21 @@ describe("MatrixClient request hardening", () => {
await expect(client.downloadContent("mxc://example.org/media")).resolves.toEqual(payload);
expect(fetchMock).toHaveBeenCalledTimes(1);
const firstUrl = String((fetchMock.mock.calls as unknown[][])[0]?.[0] ?? "");
const firstInput = (fetchMock.mock.calls as Array<[RequestInfo | URL]>)[0]?.[0];
const firstUrl =
typeof firstInput === "string"
? firstInput
: firstInput instanceof URL
? firstInput.toString()
: (firstInput?.url ?? "");
expect(firstUrl).toContain("/_matrix/client/v1/media/download/example.org/media");
});
it("falls back to legacy media downloads for older homeservers", async () => {
const payload = Buffer.from([5, 6, 7, 8]);
const fetchMock = vi.fn(async (input: RequestInfo | URL) => {
const url = String(input);
const url =
typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
if (url.includes("/_matrix/client/v1/media/download/")) {
return new Response(
JSON.stringify({
@@ -287,8 +294,21 @@ describe("MatrixClient request hardening", () => {
await expect(client.downloadContent("mxc://example.org/media")).resolves.toEqual(payload);
expect(fetchMock).toHaveBeenCalledTimes(2);
const firstUrl = String((fetchMock.mock.calls as unknown[][])[0]?.[0] ?? "");
const secondUrl = String((fetchMock.mock.calls as unknown[][])[1]?.[0] ?? "");
const [firstCall, secondCall] = fetchMock.mock.calls as Array<[RequestInfo | URL]>;
const firstInput = firstCall?.[0];
const secondInput = secondCall?.[0];
const firstUrl =
typeof firstInput === "string"
? firstInput
: firstInput instanceof URL
? firstInput.toString()
: (firstInput?.url ?? "");
const secondUrl =
typeof secondInput === "string"
? secondInput
: secondInput instanceof URL
? secondInput.toString()
: (secondInput?.url ?? "");
expect(firstUrl).toContain("/_matrix/client/v1/media/download/example.org/media");
expect(secondUrl).toContain("/_matrix/media/v3/download/example.org/media");
});

View File

@@ -16,7 +16,7 @@ function createMockFetch(response?: { status?: number; body?: unknown; contentTy
const calls: Array<{ url: string; init?: RequestInit }> = [];
const mockFetch = vi.fn(async (url: string | URL | Request, init?: RequestInit) => {
const urlStr = typeof url === "string" ? url : url.toString();
const urlStr = typeof url === "string" ? url : url instanceof URL ? url.toString() : url.url;
calls.push({ url: urlStr, init });
return new Response(JSON.stringify(body), {
status,

View File

@@ -2,6 +2,10 @@ import { expect, vi } from "vitest";
import type { OpenClawConfig } from "../../runtime-api.js";
import type { MattermostFetch } from "./client.js";
function requestUrl(url: string | URL | Request): string {
return typeof url === "string" ? url : url instanceof URL ? url.toString() : url.url;
}
export function createMattermostTestConfig(): OpenClawConfig {
return {
channels: {
@@ -31,14 +35,15 @@ export function createMattermostReactionFetchMock(params: {
const removePath = `/api/v4/users/${userId}/posts/${params.postId}/reactions/${encodeURIComponent(params.emojiName)}`;
return vi.fn<typeof fetch>(async (url, init) => {
if (String(url).endsWith("/api/v4/users/me")) {
const urlText = requestUrl(url);
if (urlText.endsWith("/api/v4/users/me")) {
return new Response(JSON.stringify({ id: userId }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
if (allowAdd && String(url).endsWith("/api/v4/reactions")) {
if (allowAdd && urlText.endsWith("/api/v4/reactions")) {
expect(init?.method).toBe("POST");
const requestBody = init?.body;
if (typeof requestBody !== "string") {
@@ -59,7 +64,7 @@ export function createMattermostReactionFetchMock(params: {
);
}
if (allowRemove && String(url).endsWith(removePath)) {
if (allowRemove && urlText.endsWith(removePath)) {
expect(init?.method).toBe("DELETE");
const responseBody = params.body === undefined ? null : params.body;
return new Response(
@@ -70,7 +75,7 @@ export function createMattermostReactionFetchMock(params: {
);
}
throw new Error(`unexpected url: ${String(url)}`);
throw new Error(`unexpected url: ${urlText}`);
});
}

View File

@@ -97,7 +97,12 @@ describe("mattermost reactions", () => {
});
const usersMeCalls = fetchMock.mock.calls.filter((call) =>
String(call[0]).endsWith("/api/v4/users/me"),
(typeof call[0] === "string"
? call[0]
: call[0] instanceof URL
? call[0].toString()
: call[0].url
).endsWith("/api/v4/users/me"),
);
expect(addResult).toEqual({ ok: true });
expect(removeResult).toEqual({ ok: true });

View File

@@ -54,7 +54,7 @@ vi.mock("./runtime-api.js", () => {
isRequestBodyLimitError: vi.fn(() => false),
logTypingFailure: vi.fn(),
formatInboundFromLabel: vi.fn(() => ""),
rawDataToString: vi.fn((value: unknown) => String(value ?? "")),
rawDataToString: vi.fn((value: unknown) => (typeof value === "string" ? value : "")),
readRequestBodyWithLimit: mockState.readRequestBodyWithLimit,
resolveThreadSessionKeys: vi.fn((params: { baseSessionKey: string }) => ({
sessionKey: params.baseSessionKey,

View File

@@ -313,7 +313,7 @@ describe("memory-core dreaming phases", () => {
}
const dailyReadCount = readSpy.mock.calls.filter(
([target]) => String(target) === dailyPath,
([target]) => typeof target === "string" && target === dailyPath,
).length;
expect(dailyReadCount).toBeLessThanOrEqual(1);
await expect(
@@ -461,7 +461,7 @@ describe("memory-core dreaming phases", () => {
);
} finally {
transcriptReadCount = readSpy.mock.calls.filter(
([target]) => String(target) === transcriptPath,
([target]) => typeof target === "string" && target === transcriptPath,
).length;
readSpy.mockRestore();
vi.unstubAllEnvs();

View File

@@ -263,7 +263,8 @@ describe("msteams graph attachments", () => {
const seen: Array<{ url: string; auth: string }> = [];
const referenceAttachment = createReferenceAttachment();
const fetchMock = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
const url = String(input);
const url =
typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
const auth = new Headers(init?.headers).get("Authorization") ?? "";
seen.push({ url, auth });

View File

@@ -531,7 +531,8 @@ describe("msteams attachments", () => {
it("blocks redirects to non-https URLs", async () => {
const insecureUrl = "http://x/insecure.png";
const fetchMock = vi.fn(async (input: RequestInfo | URL) => {
const url = typeof input === "string" ? input : input.toString();
const url =
typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
if (url === TEST_URL_IMAGE) {
return createRedirectResponse(insecureUrl);
}
@@ -560,7 +561,8 @@ describe("msteams attachments", () => {
const createGraphSharesFetchMock = () =>
vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
const url = typeof input === "string" ? input : input.toString();
const url =
typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
const auth = new Headers(init?.headers).get("Authorization");
if (url.startsWith(GRAPH_SHARES_URL_PREFIX)) {
if (!auth) {
@@ -617,7 +619,8 @@ describe("msteams attachments", () => {
expect(media[0]?.path).toBe(SAVED_PDF_PATH);
// The only host that should be fetched is graph.microsoft.com.
const calledUrls = (fetchMock.mock.calls as Array<[RequestInfo | URL, RequestInit?]>).map(
([input]) => (typeof input === "string" ? input : String(input)),
([input]) =>
typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url,
);
expect(calledUrls.length).toBeGreaterThan(0);
for (const url of calledUrls) {
@@ -646,7 +649,11 @@ describe("msteams attachments", () => {
expectAttachmentMediaLength(media, 1);
const calledUrls = (fetchMock.mock.calls as unknown[]).map((call) => {
const input = (call as [RequestInfo | URL])[0];
return typeof input === "string" ? input : String(input);
return typeof input === "string"
? input
: input instanceof URL
? input.toString()
: input.url;
});
// Should have hit the original host, NOT graph shares.
expect(calledUrls.some((url) => url === directUrl)).toBe(true);

View File

@@ -87,11 +87,15 @@ function mockGraphCollection<T>(...items: T[]) {
}
function requestUrl(input: string | URL | Request) {
return typeof input === "string" ? input : String(input);
return typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
}
function fetchCallUrl(index: number) {
return String(vi.mocked(globalThis.fetch).mock.calls[index]?.[0]);
const input = vi.mocked(globalThis.fetch).mock.calls[index]?.[0];
if (!input) {
return "";
}
return requestUrl(input);
}
function expectFetchPathContains(index: number, expectedPath: string) {

View File

@@ -5,7 +5,7 @@ import { createMSTeamsSetupWizardBase, msteamsSetupAdapter } from "./setup-core.
const resolveMSTeamsUserAllowlist = vi.hoisted(() => vi.fn());
const resolveMSTeamsChannelAllowlist = vi.hoisted(() => vi.fn());
const normalizeSecretInputString = vi.hoisted(() =>
vi.fn((value: unknown) => String(value ?? "").trim() || undefined),
vi.fn((value: unknown) => (typeof value === "string" ? value.trim() || undefined : undefined)),
);
const hasConfiguredMSTeamsCredentials = vi.hoisted(() => vi.fn());
const resolveMSTeamsCredentials = vi.hoisted(() => vi.fn());

View File

@@ -98,13 +98,13 @@ function createMockResponse(): ServerResponse & {
});
res.write = function (chunk: unknown) {
data += String(chunk);
data += typeof chunk === "string" ? chunk : Buffer.isBuffer(chunk) ? chunk.toString() : "";
return true;
};
res.end = function (chunk?: unknown) {
if (chunk) {
data += String(chunk);
data += typeof chunk === "string" ? chunk : Buffer.isBuffer(chunk) ? chunk.toString() : "";
}
return this;
};

View File

@@ -127,7 +127,11 @@ describe("buildOpenAISpeechProvider", () => {
const provider = buildOpenAISpeechProvider();
const fetchMock = vi.fn(async (_url: string, init?: RequestInit) => {
expect(init?.body).toBeTruthy();
const body = JSON.parse(String(init?.body)) as { response_format?: string };
const requestBody = init?.body;
if (typeof requestBody !== "string") {
throw new Error("expected string request body");
}
const body = JSON.parse(requestBody) as { response_format?: string };
expect(body.response_format).toBe("wav");
return new Response(new Uint8Array([1, 2, 3]), { status: 200 });
});
@@ -155,7 +159,11 @@ describe("buildOpenAISpeechProvider", () => {
const provider = buildOpenAISpeechProvider();
const fetchMock = vi.fn(async (_url: string, init?: RequestInit) => {
expect(init?.body).toBeTruthy();
const body = JSON.parse(String(init?.body)) as { response_format?: string };
const requestBody = init?.body;
if (typeof requestBody !== "string") {
throw new Error("expected string request body");
}
const body = JSON.parse(requestBody) as { response_format?: string };
expect(body.response_format).toBe("wav");
return new Response(new Uint8Array([1, 2, 3]), { status: 200 });
});

View File

@@ -142,7 +142,7 @@ describe("enforceTelegramDmAccess", () => {
expect(sendMessage).toHaveBeenCalledTimes(1);
const [firstCall] = sendMessage.mock.calls as Array<unknown[]>;
expect(firstCall?.[0]).toBe(42);
const sentText = String(firstCall?.[1] ?? "");
const sentText = typeof firstCall?.[1] === "string" ? firstCall[1] : "";
expect(sentText).toContain("Pairing code:");
expect(firstCall?.[2]).toEqual(expect.objectContaining({ parse_mode: "HTML" }));
expect(logger.info).toHaveBeenCalledWith(

View File

@@ -37,7 +37,9 @@ describe("twilioApiRequest", () => {
},
}),
);
expect(String(init?.body)).toBe(
const requestBody = init?.body;
expect(requestBody).toBeInstanceOf(URLSearchParams);
expect((requestBody as URLSearchParams).toString()).toBe(
"To=%2B14155550123&StatusCallbackEvent=initiated&StatusCallbackEvent=completed",
);
});

View File

@@ -38,7 +38,7 @@ vi.mock("./channel-react-action.runtime.js", async () => {
}
return undefined;
}
const text = String(value);
const text = typeof value === "string" ? value : "";
if (!options?.allowEmpty && !text.trim()) {
if (options?.required) {
const err = new Error(`${key} required`);

View File

@@ -107,7 +107,8 @@ describe("refreshChatAvatar", () => {
const mainRequest = createDeferred<{ avatarUrl?: string }>();
const opsRequest = createDeferred<{ avatarUrl?: string }>();
const fetchMock = vi.fn((input: string | URL | Request) => {
const url = String(input);
const url =
typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
if (url === "avatar/main?meta=1") {
return Promise.resolve({
ok: true,