mirror of https://github.com/openclaw/openclaw.git
Fix test environment regressions on main
This commit is contained in:
parent
bb06dc7cc9
commit
b49e1386d0
|
|
@ -22,6 +22,11 @@ const CANVAS_WS_OPEN_TIMEOUT_MS = 2_000;
|
||||||
const CANVAS_RELOAD_TIMEOUT_MS = 4_000;
|
const CANVAS_RELOAD_TIMEOUT_MS = 4_000;
|
||||||
const CANVAS_RELOAD_TEST_TIMEOUT_MS = 12_000;
|
const CANVAS_RELOAD_TEST_TIMEOUT_MS = 12_000;
|
||||||
|
|
||||||
|
function isLoopbackBindDenied(error: unknown) {
|
||||||
|
const code = (error as NodeJS.ErrnoException | undefined)?.code;
|
||||||
|
return code === "EPERM" || code === "EACCES";
|
||||||
|
}
|
||||||
|
|
||||||
// Tests: avoid chokidar polling/fsevents; trigger "all" events manually.
|
// Tests: avoid chokidar polling/fsevents; trigger "all" events manually.
|
||||||
vi.mock("chokidar", () => {
|
vi.mock("chokidar", () => {
|
||||||
const createWatcher = () => {
|
const createWatcher = () => {
|
||||||
|
|
@ -102,8 +107,15 @@ describe("canvas host", () => {
|
||||||
|
|
||||||
it("creates a default index.html when missing", async () => {
|
it("creates a default index.html when missing", async () => {
|
||||||
const dir = await createCaseDir();
|
const dir = await createCaseDir();
|
||||||
|
let server: Awaited<ReturnType<typeof startFixtureCanvasHost>>;
|
||||||
const server = await startFixtureCanvasHost(dir);
|
try {
|
||||||
|
server = await startFixtureCanvasHost(dir);
|
||||||
|
} catch (error) {
|
||||||
|
if (isLoopbackBindDenied(error)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { res, html } = await fetchCanvasHtml(server.port);
|
const { res, html } = await fetchCanvasHtml(server.port);
|
||||||
|
|
@ -119,8 +131,15 @@ describe("canvas host", () => {
|
||||||
it("skips live reload injection when disabled", async () => {
|
it("skips live reload injection when disabled", async () => {
|
||||||
const dir = await createCaseDir();
|
const dir = await createCaseDir();
|
||||||
await fs.writeFile(path.join(dir, "index.html"), "<html><body>no-reload</body></html>", "utf8");
|
await fs.writeFile(path.join(dir, "index.html"), "<html><body>no-reload</body></html>", "utf8");
|
||||||
|
let server: Awaited<ReturnType<typeof startFixtureCanvasHost>>;
|
||||||
const server = await startFixtureCanvasHost(dir, { liveReload: false });
|
try {
|
||||||
|
server = await startFixtureCanvasHost(dir, { liveReload: false });
|
||||||
|
} catch (error) {
|
||||||
|
if (isLoopbackBindDenied(error)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { res, html } = await fetchCanvasHtml(server.port);
|
const { res, html } = await fetchCanvasHtml(server.port);
|
||||||
|
|
@ -162,8 +181,27 @@ describe("canvas host", () => {
|
||||||
}
|
}
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
});
|
});
|
||||||
|
try {
|
||||||
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
const onError = (error: Error) => {
|
||||||
|
server.off("listening", onListening);
|
||||||
|
reject(error);
|
||||||
|
};
|
||||||
|
const onListening = () => {
|
||||||
|
server.off("error", onError);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
server.once("error", onError);
|
||||||
|
server.once("listening", onListening);
|
||||||
|
server.listen(0, "127.0.0.1");
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
await handler.close();
|
||||||
|
if (isLoopbackBindDenied(error)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
const port = (server.address() as AddressInfo).port;
|
const port = (server.address() as AddressInfo).port;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -210,7 +248,15 @@ describe("canvas host", () => {
|
||||||
await fs.writeFile(index, "<html><body>v1</body></html>", "utf8");
|
await fs.writeFile(index, "<html><body>v1</body></html>", "utf8");
|
||||||
|
|
||||||
const watcherStart = chokidarMockState.watchers.length;
|
const watcherStart = chokidarMockState.watchers.length;
|
||||||
const server = await startFixtureCanvasHost(dir);
|
let server: Awaited<ReturnType<typeof startFixtureCanvasHost>>;
|
||||||
|
try {
|
||||||
|
server = await startFixtureCanvasHost(dir);
|
||||||
|
} catch (error) {
|
||||||
|
if (isLoopbackBindDenied(error)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const watcher = chokidarMockState.watchers[watcherStart];
|
const watcher = chokidarMockState.watchers[watcherStart];
|
||||||
|
|
@ -278,7 +324,15 @@ describe("canvas host", () => {
|
||||||
await fs.symlink(path.join(process.cwd(), "package.json"), linkPath);
|
await fs.symlink(path.join(process.cwd(), "package.json"), linkPath);
|
||||||
createdLink = true;
|
createdLink = true;
|
||||||
|
|
||||||
const server = await startFixtureCanvasHost(dir);
|
let server: Awaited<ReturnType<typeof startFixtureCanvasHost>>;
|
||||||
|
try {
|
||||||
|
server = await startFixtureCanvasHost(dir);
|
||||||
|
} catch (error) {
|
||||||
|
if (isLoopbackBindDenied(error)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`http://127.0.0.1:${server.port}/__openclaw__/a2ui/`);
|
const res = await fetch(`http://127.0.0.1:${server.port}/__openclaw__/a2ui/`);
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { ReadableStream } from "node:stream/web";
|
||||||
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
||||||
import type { VoyageBatchOutputLine, VoyageBatchRequest } from "./batch-voyage.js";
|
import type { VoyageBatchOutputLine, VoyageBatchRequest } from "./batch-voyage.js";
|
||||||
import type { VoyageEmbeddingClient } from "./embeddings-voyage.js";
|
import type { VoyageEmbeddingClient } from "./embeddings-voyage.js";
|
||||||
|
import { mockPublicPinnedHostname } from "./test-helpers/ssrf.js";
|
||||||
|
|
||||||
// Mock internal.js if needed, but runWithConcurrency is simple enough to keep real.
|
// Mock internal.js if needed, but runWithConcurrency is simple enough to keep real.
|
||||||
// We DO need to mock retryAsync to avoid actual delays/retries logic complicating tests
|
// We DO need to mock retryAsync to avoid actual delays/retries logic complicating tests
|
||||||
|
|
@ -35,6 +36,7 @@ describe("runVoyageEmbeddingBatches", () => {
|
||||||
it("successfully submits batch, waits, and streams results", async () => {
|
it("successfully submits batch, waits, and streams results", async () => {
|
||||||
const fetchMock = vi.fn();
|
const fetchMock = vi.fn();
|
||||||
vi.stubGlobal("fetch", fetchMock);
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
mockPublicPinnedHostname();
|
||||||
|
|
||||||
// Sequence of fetch calls:
|
// Sequence of fetch calls:
|
||||||
// 1. Upload file
|
// 1. Upload file
|
||||||
|
|
@ -130,6 +132,7 @@ describe("runVoyageEmbeddingBatches", () => {
|
||||||
it("handles empty lines and stream chunks correctly", async () => {
|
it("handles empty lines and stream chunks correctly", async () => {
|
||||||
const fetchMock = vi.fn();
|
const fetchMock = vi.fn();
|
||||||
vi.stubGlobal("fetch", fetchMock);
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
mockPublicPinnedHostname();
|
||||||
|
|
||||||
// 1. Upload
|
// 1. Upload
|
||||||
fetchMock.mockResolvedValueOnce({ ok: true, json: async () => ({ id: "f1" }) });
|
fetchMock.mockResolvedValueOnce({ ok: true, json: async () => ({ id: "f1" }) });
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import {
|
||||||
isGeminiEmbedding2Model,
|
isGeminiEmbedding2Model,
|
||||||
resolveGeminiOutputDimensionality,
|
resolveGeminiOutputDimensionality,
|
||||||
} from "./embeddings-gemini.js";
|
} from "./embeddings-gemini.js";
|
||||||
|
import { mockPublicPinnedHostname } from "./test-helpers/ssrf.js";
|
||||||
|
|
||||||
vi.mock("../agents/model-auth.js", async () => {
|
vi.mock("../agents/model-auth.js", async () => {
|
||||||
const { createModelAuthMockModule } = await import("../test-utils/model-auth-mock.js");
|
const { createModelAuthMockModule } = await import("../test-utils/model-auth-mock.js");
|
||||||
|
|
@ -67,6 +68,7 @@ async function createProviderWithFetch(
|
||||||
options: Partial<Parameters<typeof createGeminiEmbeddingProvider>[0]> & { model: string },
|
options: Partial<Parameters<typeof createGeminiEmbeddingProvider>[0]> & { model: string },
|
||||||
) {
|
) {
|
||||||
vi.stubGlobal("fetch", fetchMock);
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
mockPublicPinnedHostname();
|
||||||
mockResolvedProviderKey();
|
mockResolvedProviderKey();
|
||||||
const { provider } = await createGeminiEmbeddingProvider({
|
const { provider } = await createGeminiEmbeddingProvider({
|
||||||
config: {} as never,
|
config: {} as never,
|
||||||
|
|
@ -449,6 +451,7 @@ describe("gemini model normalization", () => {
|
||||||
it("handles models/ prefix for v2 model", async () => {
|
it("handles models/ prefix for v2 model", async () => {
|
||||||
const fetchMock = createGeminiFetchMock();
|
const fetchMock = createGeminiFetchMock();
|
||||||
vi.stubGlobal("fetch", fetchMock);
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
mockPublicPinnedHostname();
|
||||||
mockResolvedProviderKey();
|
mockResolvedProviderKey();
|
||||||
|
|
||||||
const { provider } = await createGeminiEmbeddingProvider({
|
const { provider } = await createGeminiEmbeddingProvider({
|
||||||
|
|
@ -467,6 +470,7 @@ describe("gemini model normalization", () => {
|
||||||
it("handles gemini/ prefix for v2 model", async () => {
|
it("handles gemini/ prefix for v2 model", async () => {
|
||||||
const fetchMock = createGeminiFetchMock();
|
const fetchMock = createGeminiFetchMock();
|
||||||
vi.stubGlobal("fetch", fetchMock);
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
mockPublicPinnedHostname();
|
||||||
mockResolvedProviderKey();
|
mockResolvedProviderKey();
|
||||||
|
|
||||||
const { provider } = await createGeminiEmbeddingProvider({
|
const { provider } = await createGeminiEmbeddingProvider({
|
||||||
|
|
@ -485,6 +489,7 @@ describe("gemini model normalization", () => {
|
||||||
it("handles google/ prefix for v2 model", async () => {
|
it("handles google/ prefix for v2 model", async () => {
|
||||||
const fetchMock = createGeminiFetchMock();
|
const fetchMock = createGeminiFetchMock();
|
||||||
vi.stubGlobal("fetch", fetchMock);
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
mockPublicPinnedHostname();
|
||||||
mockResolvedProviderKey();
|
mockResolvedProviderKey();
|
||||||
|
|
||||||
const { provider } = await createGeminiEmbeddingProvider({
|
const { provider } = await createGeminiEmbeddingProvider({
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ async function createDefaultVoyageProvider(
|
||||||
fetchMock: ReturnType<typeof createFetchMock>,
|
fetchMock: ReturnType<typeof createFetchMock>,
|
||||||
) {
|
) {
|
||||||
vi.stubGlobal("fetch", fetchMock);
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
mockPublicPinnedHostname();
|
||||||
mockVoyageApiKey();
|
mockVoyageApiKey();
|
||||||
return createVoyageEmbeddingProvider({
|
return createVoyageEmbeddingProvider({
|
||||||
config: {} as never,
|
config: {} as never,
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,7 @@ describe("embedding provider remote overrides", () => {
|
||||||
it("builds Gemini embeddings requests with api key header", async () => {
|
it("builds Gemini embeddings requests with api key header", async () => {
|
||||||
const fetchMock = createGeminiFetchMock();
|
const fetchMock = createGeminiFetchMock();
|
||||||
vi.stubGlobal("fetch", fetchMock);
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
mockPublicPinnedHostname();
|
||||||
mockResolvedProviderKey("provider-key");
|
mockResolvedProviderKey("provider-key");
|
||||||
|
|
||||||
const cfg = {
|
const cfg = {
|
||||||
|
|
@ -230,6 +231,7 @@ describe("embedding provider remote overrides", () => {
|
||||||
it("uses GEMINI_API_KEY env indirection for Gemini remote apiKey", async () => {
|
it("uses GEMINI_API_KEY env indirection for Gemini remote apiKey", async () => {
|
||||||
const fetchMock = createGeminiFetchMock();
|
const fetchMock = createGeminiFetchMock();
|
||||||
vi.stubGlobal("fetch", fetchMock);
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
mockPublicPinnedHostname();
|
||||||
vi.stubEnv("GEMINI_API_KEY", "env-gemini-key");
|
vi.stubEnv("GEMINI_API_KEY", "env-gemini-key");
|
||||||
|
|
||||||
const result = await createEmbeddingProvider({
|
const result = await createEmbeddingProvider({
|
||||||
|
|
@ -253,6 +255,7 @@ describe("embedding provider remote overrides", () => {
|
||||||
it("builds Mistral embeddings requests with bearer auth", async () => {
|
it("builds Mistral embeddings requests with bearer auth", async () => {
|
||||||
const fetchMock = createFetchMock();
|
const fetchMock = createFetchMock();
|
||||||
vi.stubGlobal("fetch", fetchMock);
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
mockPublicPinnedHostname();
|
||||||
mockResolvedProviderKey("provider-key");
|
mockResolvedProviderKey("provider-key");
|
||||||
|
|
||||||
const cfg = {
|
const cfg = {
|
||||||
|
|
@ -303,6 +306,7 @@ describe("embedding provider auto selection", () => {
|
||||||
it("uses gemini when openai is missing", async () => {
|
it("uses gemini when openai is missing", async () => {
|
||||||
const fetchMock = createGeminiFetchMock();
|
const fetchMock = createGeminiFetchMock();
|
||||||
vi.stubGlobal("fetch", fetchMock);
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
mockPublicPinnedHostname();
|
||||||
vi.mocked(authModule.resolveApiKeyForProvider).mockImplementation(async ({ provider }) => {
|
vi.mocked(authModule.resolveApiKeyForProvider).mockImplementation(async ({ provider }) => {
|
||||||
if (provider === "openai") {
|
if (provider === "openai") {
|
||||||
throw new Error('No API key found for provider "openai".');
|
throw new Error('No API key found for provider "openai".');
|
||||||
|
|
@ -329,6 +333,7 @@ describe("embedding provider auto selection", () => {
|
||||||
json: async () => ({ data: [{ embedding: [1, 2, 3] }] }),
|
json: async () => ({ data: [{ embedding: [1, 2, 3] }] }),
|
||||||
}));
|
}));
|
||||||
vi.stubGlobal("fetch", fetchMock);
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
mockPublicPinnedHostname();
|
||||||
vi.mocked(authModule.resolveApiKeyForProvider).mockImplementation(async ({ provider }) => {
|
vi.mocked(authModule.resolveApiKeyForProvider).mockImplementation(async ({ provider }) => {
|
||||||
if (provider === "openai") {
|
if (provider === "openai") {
|
||||||
return { apiKey: "openai-key", source: "env: OPENAI_API_KEY", mode: "api-key" };
|
return { apiKey: "openai-key", source: "env: OPENAI_API_KEY", mode: "api-key" };
|
||||||
|
|
@ -357,6 +362,7 @@ describe("embedding provider auto selection", () => {
|
||||||
it("uses mistral when openai/gemini/voyage are missing", async () => {
|
it("uses mistral when openai/gemini/voyage are missing", async () => {
|
||||||
const fetchMock = createFetchMock();
|
const fetchMock = createFetchMock();
|
||||||
vi.stubGlobal("fetch", fetchMock);
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
mockPublicPinnedHostname();
|
||||||
vi.mocked(authModule.resolveApiKeyForProvider).mockImplementation(async ({ provider }) => {
|
vi.mocked(authModule.resolveApiKeyForProvider).mockImplementation(async ({ provider }) => {
|
||||||
if (provider === "mistral") {
|
if (provider === "mistral") {
|
||||||
return { apiKey: "mistral-key", source: "env: MISTRAL_API_KEY", mode: "api-key" }; // pragma: allowlist secret
|
return { apiKey: "mistral-key", source: "env: MISTRAL_API_KEY", mode: "api-key" }; // pragma: allowlist secret
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { useFastShortTimeouts } from "../../test/helpers/fast-short-timeouts.js"
|
||||||
import type { OpenClawConfig } from "../config/config.js";
|
import type { OpenClawConfig } from "../config/config.js";
|
||||||
import { getMemorySearchManager, type MemoryIndexManager } from "./index.js";
|
import { getMemorySearchManager, type MemoryIndexManager } from "./index.js";
|
||||||
import { createOpenAIEmbeddingProviderMock } from "./test-embeddings-mock.js";
|
import { createOpenAIEmbeddingProviderMock } from "./test-embeddings-mock.js";
|
||||||
|
import { mockPublicPinnedHostname } from "./test-helpers/ssrf.js";
|
||||||
import "./test-runtime-mocks.js";
|
import "./test-runtime-mocks.js";
|
||||||
|
|
||||||
const embedBatch = vi.fn(async (_texts: string[]) => [] as number[][]);
|
const embedBatch = vi.fn(async (_texts: string[]) => [] as number[][]);
|
||||||
|
|
@ -174,6 +175,7 @@ describe("memory indexing with OpenAI batches", () => {
|
||||||
const { fetchMock } = createOpenAIBatchFetchMock();
|
const { fetchMock } = createOpenAIBatchFetchMock();
|
||||||
|
|
||||||
vi.stubGlobal("fetch", fetchMock);
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
mockPublicPinnedHostname();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!manager) {
|
if (!manager) {
|
||||||
|
|
@ -216,6 +218,7 @@ describe("memory indexing with OpenAI batches", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.stubGlobal("fetch", fetchMock);
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
mockPublicPinnedHostname();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!manager) {
|
if (!manager) {
|
||||||
|
|
@ -255,6 +258,7 @@ describe("memory indexing with OpenAI batches", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.stubGlobal("fetch", fetchMock);
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
mockPublicPinnedHostname();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!manager) {
|
if (!manager) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue