mirror of https://github.com/openclaw/openclaw.git
test: stabilize browser and provider ci shards
This commit is contained in:
parent
332e7d9d7b
commit
b1c98e8469
|
|
@ -1,4 +1,4 @@
|
|||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("node:child_process", async () => {
|
||||
const { mockNodeBuiltinModule } = await import("../../../../test/helpers/node-builtin-mocks.js");
|
||||
|
|
@ -31,11 +31,11 @@ vi.mock("node:os", async () => {
|
|||
import { execFileSync } from "node:child_process";
|
||||
import * as fs from "node:fs";
|
||||
import os from "node:os";
|
||||
const { resolveBrowserExecutableForPlatform } = await import("./chrome.executables.js");
|
||||
|
||||
describe("browser default executable detection", () => {
|
||||
const launchServicesPlist = "com.apple.launchservices.secure.plist";
|
||||
const chromeExecutablePath = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
|
||||
let resolveBrowserExecutableForPlatform: typeof import("./chrome.executables.js").resolveBrowserExecutableForPlatform;
|
||||
|
||||
function mockMacDefaultBrowser(bundleId: string, appPath = ""): void {
|
||||
vi.mocked(execFileSync).mockImplementation((cmd, args) => {
|
||||
|
|
@ -63,10 +63,6 @@ describe("browser default executable detection", () => {
|
|||
});
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
({ resolveBrowserExecutableForPlatform } = await import("./chrome.executables.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(os.homedir).mockReturnValue("/Users/test");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { BrowserDispatchResponse } from "./routes/dispatcher.js";
|
||||
|
||||
function okDispatchResponse(): BrowserDispatchResponse {
|
||||
|
|
@ -49,7 +49,7 @@ vi.mock("./routes/dispatcher.js", () => ({
|
|||
})),
|
||||
}));
|
||||
|
||||
let fetchBrowserJson: typeof import("./client-fetch.js").fetchBrowserJson;
|
||||
const { fetchBrowserJson } = await import("./client-fetch.js");
|
||||
|
||||
function stubJsonFetchOk() {
|
||||
const fetchMock = vi.fn<(input: RequestInfo | URL, init?: RequestInit) => Promise<Response>>(
|
||||
|
|
@ -85,10 +85,6 @@ async function expectThrownBrowserFetchError(
|
|||
}
|
||||
|
||||
describe("fetchBrowserJson loopback auth", () => {
|
||||
beforeAll(async () => {
|
||||
({ fetchBrowserJson } = await import("./client-fetch.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.stubEnv("OPENCLAW_GATEWAY_TOKEN", "loopback-token");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
ensureBrowserControlAuth: vi.fn(async () => ({ generatedToken: false })),
|
||||
|
|
@ -42,13 +42,9 @@ vi.mock("./runtime-lifecycle.js", () => ({
|
|||
stopBrowserRuntime: vi.fn(async () => {}),
|
||||
}));
|
||||
|
||||
let startBrowserControlServiceFromConfig: typeof import("../control-service.js").startBrowserControlServiceFromConfig;
|
||||
const { startBrowserControlServiceFromConfig } = await import("../control-service.js");
|
||||
|
||||
describe("startBrowserControlServiceFromConfig", () => {
|
||||
beforeAll(async () => {
|
||||
({ startBrowserControlServiceFromConfig } = await import("../control-service.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
mocks.ensureBrowserControlAuth.mockClear();
|
||||
mocks.createBrowserRuntimeState.mockClear();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { loadConfig, writeConfigFile } from "../config/config.js";
|
||||
import { resolveOpenClawUserDataDir } from "./chrome.js";
|
||||
import type { BrowserRouteContext, BrowserServerState } from "./server-context.js";
|
||||
|
|
@ -23,8 +23,10 @@ vi.mock("./chrome.js", () => ({
|
|||
resolveOpenClawUserDataDir: vi.fn(() => "/tmp/openclaw-test/openclaw/user-data"),
|
||||
}));
|
||||
|
||||
let resolveBrowserConfig: typeof import("./config.js").resolveBrowserConfig;
|
||||
let createBrowserProfilesService: typeof import("./profiles-service.js").createBrowserProfilesService;
|
||||
const [{ resolveBrowserConfig }, { createBrowserProfilesService }] = await Promise.all([
|
||||
import("./config.js"),
|
||||
import("./profiles-service.js"),
|
||||
]);
|
||||
|
||||
function createCtx(resolved: BrowserServerState["resolved"]) {
|
||||
const state: BrowserServerState = {
|
||||
|
|
@ -57,11 +59,6 @@ async function createWorkProfileWithConfig(params: {
|
|||
}
|
||||
|
||||
describe("BrowserProfilesService", () => {
|
||||
beforeAll(async () => {
|
||||
({ resolveBrowserConfig } = await import("./config.js"));
|
||||
({ createBrowserProfilesService } = await import("./profiles-service.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
installPwToolsCoreTestHooks,
|
||||
setPwToolsCoreCurrentPage,
|
||||
|
|
@ -6,13 +6,9 @@ import {
|
|||
} from "./pw-tools-core.test-harness.js";
|
||||
|
||||
installPwToolsCoreTestHooks();
|
||||
let mod: typeof import("./pw-tools-core.js");
|
||||
const mod = await import("./pw-tools-core.js");
|
||||
|
||||
describe("pw-tools-core", () => {
|
||||
beforeAll(async () => {
|
||||
mod = await import("./pw-tools-core.js");
|
||||
});
|
||||
|
||||
it("clamps timeoutMs for scrollIntoView", async () => {
|
||||
const scrollIntoViewIfNeeded = vi.fn(async () => {});
|
||||
setPwToolsCoreCurrentRefLocator({ scrollIntoViewIfNeeded });
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
let page: { evaluate: ReturnType<typeof vi.fn> } | null = null;
|
||||
|
||||
|
|
@ -31,13 +31,9 @@ vi.mock("./pw-tools-core.snapshot.js", () => ({
|
|||
resizeViewportViaPlaywright,
|
||||
}));
|
||||
|
||||
let batchViaPlaywright: typeof import("./pw-tools-core.interactions.js").batchViaPlaywright;
|
||||
const { batchViaPlaywright } = await import("./pw-tools-core.interactions.js");
|
||||
|
||||
describe("batchViaPlaywright", () => {
|
||||
beforeAll(async () => {
|
||||
({ batchViaPlaywright } = await import("./pw-tools-core.interactions.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
page = {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
let page: { evaluate: ReturnType<typeof vi.fn> } | null = null;
|
||||
let locator: { evaluate: ReturnType<typeof vi.fn> } | null = null;
|
||||
|
|
@ -29,7 +29,7 @@ vi.mock("./pw-session.js", () => {
|
|||
};
|
||||
});
|
||||
|
||||
let evaluateViaPlaywright: typeof import("./pw-tools-core.interactions.js").evaluateViaPlaywright;
|
||||
const { evaluateViaPlaywright } = await import("./pw-tools-core.interactions.js");
|
||||
|
||||
function createPendingEval() {
|
||||
let evalCalled!: () => void;
|
||||
|
|
@ -43,10 +43,6 @@ function createPendingEval() {
|
|||
}
|
||||
|
||||
describe("evaluateViaPlaywright (abort)", () => {
|
||||
beforeAll(async () => {
|
||||
({ evaluateViaPlaywright } = await import("./pw-tools-core.interactions.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
page = null;
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ vi.mock("./paths.js", () => {
|
|||
};
|
||||
});
|
||||
|
||||
let setInputFilesViaPlaywright: typeof import("./pw-tools-core.interactions.js").setInputFilesViaPlaywright;
|
||||
const { setInputFilesViaPlaywright } = await import("./pw-tools-core.interactions.js");
|
||||
|
||||
function seedSingleLocatorPage(): { setInputFiles: ReturnType<typeof vi.fn> } {
|
||||
const setInputFiles = vi.fn(async () => {});
|
||||
|
|
@ -54,9 +54,7 @@ function seedSingleLocatorPage(): { setInputFiles: ReturnType<typeof vi.fn> } {
|
|||
}
|
||||
|
||||
describe("setInputFilesViaPlaywright", () => {
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
({ setInputFilesViaPlaywright } = await import("./pw-tools-core.interactions.js"));
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
page = null;
|
||||
locator = null;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import crypto from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { DEFAULT_UPLOAD_DIR } from "./paths.js";
|
||||
import {
|
||||
installPwToolsCoreTestHooks,
|
||||
|
|
@ -9,13 +9,9 @@ import {
|
|||
} from "./pw-tools-core.test-harness.js";
|
||||
|
||||
installPwToolsCoreTestHooks();
|
||||
let mod: typeof import("./pw-tools-core.js");
|
||||
const mod = await import("./pw-tools-core.js");
|
||||
|
||||
describe("pw-tools-core", () => {
|
||||
beforeAll(async () => {
|
||||
mod = await import("./pw-tools-core.js");
|
||||
});
|
||||
|
||||
it("last file-chooser arm wins", async () => {
|
||||
const firstPath = path.join(DEFAULT_UPLOAD_DIR, `vitest-arm-1-${crypto.randomUUID()}.txt`);
|
||||
const secondPath = path.join(DEFAULT_UPLOAD_DIR, `vitest-arm-2-${crypto.randomUUID()}.txt`);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import crypto from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { DEFAULT_UPLOAD_DIR } from "./paths.js";
|
||||
import {
|
||||
getPwToolsCoreSessionMocks,
|
||||
|
|
@ -12,7 +12,7 @@ import {
|
|||
|
||||
installPwToolsCoreTestHooks();
|
||||
const sessionMocks = getPwToolsCoreSessionMocks();
|
||||
let mod: typeof import("./pw-tools-core.js");
|
||||
const mod = await import("./pw-tools-core.js");
|
||||
|
||||
function createFileChooserPageMocks() {
|
||||
const fileChooser = { setFiles: vi.fn(async () => {}) };
|
||||
|
|
@ -26,10 +26,6 @@ function createFileChooserPageMocks() {
|
|||
}
|
||||
|
||||
describe("pw-tools-core", () => {
|
||||
beforeAll(async () => {
|
||||
mod = await import("./pw-tools-core.js");
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createBrowserRouteApp, createBrowserRouteResponse } from "./test-helpers.js";
|
||||
import type { BrowserRequest } from "./types.js";
|
||||
|
||||
|
|
@ -94,13 +94,8 @@ vi.mock("./agent.shared.js", () => ({
|
|||
}),
|
||||
}));
|
||||
|
||||
let registerBrowserAgentActRoutes: typeof import("./agent.act.js").registerBrowserAgentActRoutes;
|
||||
let registerBrowserAgentSnapshotRoutes: typeof import("./agent.snapshot.js").registerBrowserAgentSnapshotRoutes;
|
||||
|
||||
beforeAll(async () => {
|
||||
({ registerBrowserAgentActRoutes } = await import("./agent.act.js"));
|
||||
({ registerBrowserAgentSnapshotRoutes } = await import("./agent.snapshot.js"));
|
||||
});
|
||||
const { registerBrowserAgentActRoutes } = await import("./agent.act.js");
|
||||
const { registerBrowserAgentSnapshotRoutes } = await import("./agent.snapshot.js");
|
||||
|
||||
function getSnapshotGetHandler() {
|
||||
const { app, getHandlers } = createBrowserRouteApp();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createBrowserRouteApp, createBrowserRouteResponse } from "./test-helpers.js";
|
||||
|
||||
vi.mock("../chrome-mcp.js", () => ({
|
||||
getChromeMcpPid: vi.fn(() => 4321),
|
||||
}));
|
||||
|
||||
let registerBrowserBasicRoutes: typeof import("./basic.js").registerBrowserBasicRoutes;
|
||||
let BrowserProfileUnavailableError: typeof import("../errors.js").BrowserProfileUnavailableError;
|
||||
const { BrowserProfileUnavailableError } = await import("../errors.js");
|
||||
const { registerBrowserBasicRoutes } = await import("./basic.js");
|
||||
|
||||
function createExistingSessionProfileState(params?: { isHttpReachable?: () => Promise<boolean> }) {
|
||||
return {
|
||||
|
|
@ -52,11 +52,6 @@ async function callBasicRouteWithState(params: {
|
|||
return response;
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
({ BrowserProfileUnavailableError } = await import("../errors.js"));
|
||||
({ registerBrowserBasicRoutes } = await import("./basic.js"));
|
||||
});
|
||||
|
||||
describe("basic browser routes", () => {
|
||||
it("maps existing-session status failures to JSON browser errors", async () => {
|
||||
const response = await callBasicRouteWithState({
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import fs from "node:fs";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { BrowserServerState } from "./server-context.js";
|
||||
|
||||
vi.mock("./chrome-mcp.js", () => ({
|
||||
|
|
@ -19,8 +19,8 @@ vi.mock("./chrome-mcp.js", () => ({
|
|||
getChromeMcpPid: vi.fn(() => 4321),
|
||||
}));
|
||||
|
||||
let createBrowserRouteContext: typeof import("./server-context.js").createBrowserRouteContext;
|
||||
let chromeMcp: typeof import("./chrome-mcp.js");
|
||||
const { createBrowserRouteContext } = await import("./server-context.js");
|
||||
const chromeMcp = await import("./chrome-mcp.js");
|
||||
|
||||
function makeState(): BrowserServerState {
|
||||
return {
|
||||
|
|
@ -58,11 +58,6 @@ function makeState(): BrowserServerState {
|
|||
};
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
({ createBrowserRouteContext } = await import("./server-context.js"));
|
||||
chromeMcp = await import("./chrome-mcp.js");
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -61,19 +61,13 @@ vi.mock("./config-refresh-source.js", () => ({
|
|||
loadBrowserConfigForRuntimeRefresh: () => buildConfig(),
|
||||
}));
|
||||
|
||||
describe("server-context hot-reload profiles", () => {
|
||||
let loadConfig: typeof import("../config/config.js").loadConfig;
|
||||
let resolveBrowserConfig: typeof import("./config.js").resolveBrowserConfig;
|
||||
let resolveProfile: typeof import("./config.js").resolveProfile;
|
||||
let refreshResolvedBrowserConfigFromDisk: typeof import("./resolved-config-refresh.js").refreshResolvedBrowserConfigFromDisk;
|
||||
let resolveBrowserProfileWithHotReload: typeof import("./resolved-config-refresh.js").resolveBrowserProfileWithHotReload;
|
||||
const { loadConfig } = await import("../config/config.js");
|
||||
const { resolveBrowserConfig, resolveProfile } = await import("./config.js");
|
||||
const { refreshResolvedBrowserConfigFromDisk, resolveBrowserProfileWithHotReload } =
|
||||
await import("./resolved-config-refresh.js");
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
({ loadConfig } = await import("../config/config.js"));
|
||||
({ resolveBrowserConfig, resolveProfile } = await import("./config.js"));
|
||||
({ refreshResolvedBrowserConfigFromDisk, resolveBrowserProfileWithHotReload } =
|
||||
await import("./resolved-config-refresh.js"));
|
||||
describe("server-context hot-reload profiles", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockState.cfgProfiles = {
|
||||
openclaw: { cdpPort: 18800, color: "#FF4500" },
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
loadRemoteProfileTestDeps,
|
||||
type RemoteProfileTestDeps,
|
||||
} from "./server-context.remote-profile-tab-ops.shared.js";
|
||||
|
||||
let deps: RemoteProfileTestDeps;
|
||||
|
||||
beforeAll(async () => {
|
||||
deps = await loadRemoteProfileTestDeps();
|
||||
});
|
||||
const deps: RemoteProfileTestDeps = await loadRemoteProfileTestDeps();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
loadRemoteProfileTestDeps,
|
||||
type RemoteProfileTestDeps,
|
||||
} from "./server-context.remote-profile-tab-ops.shared.js";
|
||||
|
||||
let deps: RemoteProfileTestDeps;
|
||||
|
||||
beforeAll(async () => {
|
||||
deps = await loadRemoteProfileTestDeps();
|
||||
});
|
||||
const deps: RemoteProfileTestDeps = await loadRemoteProfileTestDeps();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { stopOpenClawChromeMock } = vi.hoisted(() => ({
|
||||
stopOpenClawChromeMock: vi.fn(async () => {}),
|
||||
|
|
@ -18,13 +18,8 @@ vi.mock("./server-context.js", () => ({
|
|||
listKnownProfileNames: listKnownProfileNamesMock,
|
||||
}));
|
||||
|
||||
let ensureExtensionRelayForProfiles: typeof import("./server-lifecycle.js").ensureExtensionRelayForProfiles;
|
||||
let stopKnownBrowserProfiles: typeof import("./server-lifecycle.js").stopKnownBrowserProfiles;
|
||||
|
||||
beforeAll(async () => {
|
||||
({ ensureExtensionRelayForProfiles, stopKnownBrowserProfiles } =
|
||||
await import("./server-lifecycle.js"));
|
||||
});
|
||||
const { ensureExtensionRelayForProfiles, stopKnownBrowserProfiles } =
|
||||
await import("./server-lifecycle.js");
|
||||
|
||||
beforeEach(() => {
|
||||
createBrowserRouteContextMock.mockClear();
|
||||
|
|
|
|||
|
|
@ -400,6 +400,7 @@ export async function cleanupBrowserControlServerTestContext(): Promise<void> {
|
|||
}
|
||||
|
||||
export function installBrowserControlServerHooks() {
|
||||
const hookTimeoutMs = process.platform === "win32" ? 300_000 : 240_000;
|
||||
beforeEach(async () => {
|
||||
vi.useRealTimers();
|
||||
cdpMocks.createTargetViaCdp.mockImplementation(async () => {
|
||||
|
|
@ -463,7 +464,7 @@ export function installBrowserControlServerHooks() {
|
|||
return makeResponse({}, { ok: false, status: 500, text: "unexpected" });
|
||||
}),
|
||||
);
|
||||
});
|
||||
}, hookTimeoutMs);
|
||||
|
||||
afterEach(async () => {
|
||||
await cleanupBrowserControlServerTestContext();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { getBrowserTestFetch } from "./test-fetch.js";
|
||||
import { getFreePort } from "./test-port.js";
|
||||
|
||||
|
|
@ -65,15 +65,10 @@ vi.mock("./server-context.js", async () => {
|
|||
};
|
||||
});
|
||||
|
||||
let startBrowserControlServerFromConfig: typeof import("./server.js").startBrowserControlServerFromConfig;
|
||||
let stopBrowserControlServer: typeof import("./server.js").stopBrowserControlServer;
|
||||
const { startBrowserControlServerFromConfig, stopBrowserControlServer } =
|
||||
await import("./server.js");
|
||||
|
||||
describe("browser control evaluate gating", () => {
|
||||
beforeAll(async () => {
|
||||
({ startBrowserControlServerFromConfig, stopBrowserControlServer } =
|
||||
await import("./server.js"));
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
testPort = await getFreePort();
|
||||
prevGatewayPort = process.env.OPENCLAW_GATEWAY_PORT;
|
||||
|
|
|
|||
|
|
@ -5,22 +5,6 @@ import type { IMessageRpcClient } from "./client.js";
|
|||
import { imessageOutbound } from "./outbound-adapter.js";
|
||||
import { sendMessageIMessage } from "./send.js";
|
||||
|
||||
function requireIMessageSendText() {
|
||||
const sendText = imessagePlugin.outbound?.sendText;
|
||||
if (!sendText) {
|
||||
throw new Error("imessage outbound.sendText unavailable");
|
||||
}
|
||||
return sendText;
|
||||
}
|
||||
|
||||
function requireIMessageSendMedia() {
|
||||
const sendMedia = imessagePlugin.outbound?.sendMedia;
|
||||
if (!sendMedia) {
|
||||
throw new Error("imessage outbound.sendMedia unavailable");
|
||||
}
|
||||
return sendMedia;
|
||||
}
|
||||
|
||||
function requireIMessageChunker() {
|
||||
const chunker = imessagePlugin.outbound?.chunker;
|
||||
if (!chunker) {
|
||||
|
|
@ -63,136 +47,43 @@ function getSentParams() {
|
|||
return requestMock.mock.calls[0]?.[1] as Record<string, unknown>;
|
||||
}
|
||||
|
||||
async function expectDirectOutboundResult(params: {
|
||||
invoke: () => Promise<{ channel: string; messageId: string }>;
|
||||
sendIMessage: ReturnType<typeof vi.fn>;
|
||||
to: string;
|
||||
text: string;
|
||||
expectedOptions: Record<string, unknown>;
|
||||
expectedResult: { channel: string; messageId: string };
|
||||
}) {
|
||||
const result = await params.invoke();
|
||||
expect(params.sendIMessage).toHaveBeenCalledWith(
|
||||
params.to,
|
||||
params.text,
|
||||
expect.objectContaining(params.expectedOptions),
|
||||
);
|
||||
expect(result).toEqual(params.expectedResult);
|
||||
}
|
||||
|
||||
async function expectReplyToTextForwarding(params: {
|
||||
invoke: () => Promise<{ channel: string; messageId: string }>;
|
||||
sendIMessage: ReturnType<typeof vi.fn>;
|
||||
}) {
|
||||
await expectDirectOutboundResult({
|
||||
invoke: params.invoke,
|
||||
sendIMessage: params.sendIMessage,
|
||||
to: "chat_id:12",
|
||||
text: "hello",
|
||||
expectedOptions: {
|
||||
const result = await params.invoke();
|
||||
expect(params.sendIMessage).toHaveBeenCalledWith(
|
||||
"chat_id:12",
|
||||
"hello",
|
||||
expect.objectContaining({
|
||||
accountId: "default",
|
||||
replyToId: "reply-1",
|
||||
maxBytes: 3 * 1024 * 1024,
|
||||
},
|
||||
expectedResult: { channel: "imessage", messageId: "m-text" },
|
||||
});
|
||||
}),
|
||||
);
|
||||
expect(result).toEqual({ channel: "imessage", messageId: "m-text" });
|
||||
}
|
||||
|
||||
async function expectMediaLocalRootsForwarding(params: {
|
||||
invoke: () => Promise<{ channel: string; messageId: string }>;
|
||||
sendIMessage: ReturnType<typeof vi.fn>;
|
||||
}) {
|
||||
await expectDirectOutboundResult({
|
||||
invoke: params.invoke,
|
||||
sendIMessage: params.sendIMessage,
|
||||
to: "chat_id:88",
|
||||
text: "caption",
|
||||
expectedOptions: {
|
||||
const result = await params.invoke();
|
||||
expect(params.sendIMessage).toHaveBeenCalledWith(
|
||||
"chat_id:88",
|
||||
"caption",
|
||||
expect.objectContaining({
|
||||
mediaUrl: "/tmp/workspace/pic.png",
|
||||
mediaLocalRoots: ["/tmp/workspace"],
|
||||
accountId: "acct-1",
|
||||
replyToId: "reply-2",
|
||||
maxBytes: 3 * 1024 * 1024,
|
||||
},
|
||||
expectedResult: { channel: "imessage", messageId: "m-media-local" },
|
||||
});
|
||||
}),
|
||||
);
|
||||
expect(result).toEqual({ channel: "imessage", messageId: "m-media-local" });
|
||||
}
|
||||
|
||||
describe("imessagePlugin outbound", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
imessage: {
|
||||
mediaMaxMb: 3,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
it("forwards replyToId on direct sendText adapter path", async () => {
|
||||
const sendIMessage = vi.fn().mockResolvedValue({ messageId: "m-text" });
|
||||
const sendText = requireIMessageSendText();
|
||||
|
||||
await expectReplyToTextForwarding({
|
||||
invoke: async () =>
|
||||
await sendText({
|
||||
cfg,
|
||||
to: "chat_id:12",
|
||||
text: "hello",
|
||||
accountId: "default",
|
||||
replyToId: "reply-1",
|
||||
deps: { sendIMessage },
|
||||
}),
|
||||
sendIMessage,
|
||||
});
|
||||
});
|
||||
|
||||
it("forwards replyToId on direct sendMedia adapter path", async () => {
|
||||
const sendIMessage = vi.fn().mockResolvedValue({ messageId: "m-media" });
|
||||
const sendMedia = requireIMessageSendMedia();
|
||||
|
||||
const result = await sendMedia({
|
||||
cfg,
|
||||
to: "chat_id:77",
|
||||
text: "caption",
|
||||
mediaUrl: "https://example.com/pic.png",
|
||||
accountId: "acct-1",
|
||||
replyToId: "reply-2",
|
||||
deps: { sendIMessage },
|
||||
});
|
||||
|
||||
expect(sendIMessage).toHaveBeenCalledWith(
|
||||
"chat_id:77",
|
||||
"caption",
|
||||
expect.objectContaining({
|
||||
mediaUrl: "https://example.com/pic.png",
|
||||
accountId: "acct-1",
|
||||
replyToId: "reply-2",
|
||||
maxBytes: 3 * 1024 * 1024,
|
||||
}),
|
||||
);
|
||||
expect(result).toEqual({ channel: "imessage", messageId: "m-media" });
|
||||
});
|
||||
|
||||
it("forwards mediaLocalRoots on direct sendMedia adapter path", async () => {
|
||||
const sendIMessage = vi.fn().mockResolvedValue({ messageId: "m-media-local" });
|
||||
const sendMedia = requireIMessageSendMedia();
|
||||
const mediaLocalRoots = ["/tmp/workspace"];
|
||||
|
||||
await expectMediaLocalRootsForwarding({
|
||||
invoke: async () =>
|
||||
await sendMedia({
|
||||
cfg,
|
||||
to: "chat_id:88",
|
||||
text: "caption",
|
||||
mediaUrl: "/tmp/workspace/pic.png",
|
||||
mediaLocalRoots,
|
||||
accountId: "acct-1",
|
||||
replyToId: "reply-2",
|
||||
deps: { sendIMessage },
|
||||
}),
|
||||
sendIMessage,
|
||||
});
|
||||
});
|
||||
|
||||
it("chunks outbound text without requiring iMessage runtime initialization", () => {
|
||||
const chunker = requireIMessageChunker();
|
||||
|
||||
|
|
@ -258,146 +149,4 @@ describe("sendMessageIMessage", () => {
|
|||
expect(params.chat_id).toBe(123);
|
||||
expect(params.text).toBe("hi");
|
||||
});
|
||||
|
||||
it("applies sms service prefix", async () => {
|
||||
requestMock.mockClear().mockResolvedValue({ ok: true });
|
||||
stopMock.mockClear().mockResolvedValue(undefined);
|
||||
|
||||
await sendWithDefaults("sms:+1555", "hello");
|
||||
const params = getSentParams();
|
||||
expect(params.service).toBe("sms");
|
||||
expect(params.to).toBe("+1555");
|
||||
});
|
||||
|
||||
it("adds file attachment with placeholder text", async () => {
|
||||
requestMock.mockClear().mockResolvedValue({ ok: true });
|
||||
stopMock.mockClear().mockResolvedValue(undefined);
|
||||
|
||||
await sendWithDefaults("chat_id:7", "", {
|
||||
mediaUrl: "http://x/y.jpg",
|
||||
resolveAttachmentImpl: async () => ({
|
||||
path: "/tmp/imessage-media.jpg",
|
||||
contentType: "image/jpeg",
|
||||
}),
|
||||
});
|
||||
const params = getSentParams();
|
||||
expect(params.file).toBe("/tmp/imessage-media.jpg");
|
||||
expect(params.text).toBe("<media:image>");
|
||||
});
|
||||
|
||||
it("normalizes mixed-case parameterized MIME for attachment placeholder text", async () => {
|
||||
requestMock.mockClear().mockResolvedValue({ ok: true });
|
||||
stopMock.mockClear().mockResolvedValue(undefined);
|
||||
|
||||
await sendWithDefaults("chat_id:7", "", {
|
||||
mediaUrl: "http://x/voice",
|
||||
resolveAttachmentImpl: async () => ({
|
||||
path: "/tmp/imessage-media.ogg",
|
||||
contentType: " Audio/Ogg; codecs=opus ",
|
||||
}),
|
||||
});
|
||||
const params = getSentParams();
|
||||
expect(params.file).toBe("/tmp/imessage-media.ogg");
|
||||
expect(params.text).toBe("<media:audio>");
|
||||
});
|
||||
|
||||
it("returns message id when rpc provides one", async () => {
|
||||
requestMock.mockClear().mockResolvedValue({ ok: true, id: 123 });
|
||||
stopMock.mockClear().mockResolvedValue(undefined);
|
||||
|
||||
const result = await sendWithDefaults("chat_id:7", "hello");
|
||||
expect(result.messageId).toBe("123");
|
||||
});
|
||||
|
||||
it("passes replyToId as separate reply_to param instead of embedding in text", async () => {
|
||||
requestMock.mockClear().mockResolvedValue({ ok: true });
|
||||
stopMock.mockClear().mockResolvedValue(undefined);
|
||||
|
||||
await sendWithDefaults("chat_id:123", "hello world", {
|
||||
replyToId: "abc-123",
|
||||
});
|
||||
const params = getSentParams();
|
||||
expect(params.text).toBe("hello world");
|
||||
expect(params.reply_to).toBe("abc-123");
|
||||
});
|
||||
|
||||
it("strips inline reply tags from text and passes replyToId as reply_to param", async () => {
|
||||
requestMock.mockClear().mockResolvedValue({ ok: true });
|
||||
stopMock.mockClear().mockResolvedValue(undefined);
|
||||
|
||||
await sendWithDefaults("chat_id:123", " [[reply_to:old-id]] hello", {
|
||||
replyToId: "new-id",
|
||||
});
|
||||
const params = getSentParams();
|
||||
expect(params.text).toBe("hello");
|
||||
expect(params.reply_to).toBe("new-id");
|
||||
});
|
||||
|
||||
it("sanitizes replyToId before passing as reply_to param", async () => {
|
||||
requestMock.mockClear().mockResolvedValue({ ok: true });
|
||||
stopMock.mockClear().mockResolvedValue(undefined);
|
||||
|
||||
await sendWithDefaults("chat_id:123", "hello", {
|
||||
replyToId: " [ab]\n\u0000c\td ] ",
|
||||
});
|
||||
const params = getSentParams();
|
||||
expect(params.text).toBe("hello");
|
||||
expect(params.reply_to).toBe("abcd");
|
||||
});
|
||||
|
||||
it("omits reply_to param when sanitized replyToId is empty", async () => {
|
||||
requestMock.mockClear().mockResolvedValue({ ok: true });
|
||||
stopMock.mockClear().mockResolvedValue(undefined);
|
||||
|
||||
await sendWithDefaults("chat_id:123", "hello", {
|
||||
replyToId: "[]\u0000\n\r",
|
||||
});
|
||||
const params = getSentParams();
|
||||
expect(params.text).toBe("hello");
|
||||
expect(params.reply_to).toBeUndefined();
|
||||
});
|
||||
|
||||
it("strips stray [[reply_to:...]] tags from text even without replyToId option", async () => {
|
||||
requestMock.mockClear().mockResolvedValue({ ok: true });
|
||||
stopMock.mockClear().mockResolvedValue(undefined);
|
||||
|
||||
await sendWithDefaults("chat_id:123", "[[reply_to:65]] Great question");
|
||||
const params = getSentParams();
|
||||
expect(params.text).toBe("Great question");
|
||||
expect(params.reply_to).toBeUndefined();
|
||||
});
|
||||
|
||||
it("strips [[audio_as_voice]] tags from outbound text", async () => {
|
||||
requestMock.mockClear().mockResolvedValue({ ok: true });
|
||||
stopMock.mockClear().mockResolvedValue(undefined);
|
||||
|
||||
await sendWithDefaults("chat_id:123", "hello [[audio_as_voice]] world");
|
||||
const params = getSentParams();
|
||||
expect(params.text).toBe("hello world");
|
||||
});
|
||||
|
||||
it("throws when text is only directive tags and no media", async () => {
|
||||
requestMock.mockClear().mockResolvedValue({ ok: true });
|
||||
stopMock.mockClear().mockResolvedValue(undefined);
|
||||
|
||||
await expect(sendWithDefaults("chat_id:123", "[[reply_to:65]]")).rejects.toThrow(
|
||||
"iMessage send requires text or media",
|
||||
);
|
||||
});
|
||||
|
||||
it("normalizes string message_id values from rpc result", async () => {
|
||||
requestMock.mockClear().mockResolvedValue({ ok: true, message_id: " guid-1 " });
|
||||
stopMock.mockClear().mockResolvedValue(undefined);
|
||||
|
||||
const result = await sendWithDefaults("chat_id:7", "hello");
|
||||
expect(result.messageId).toBe("guid-1");
|
||||
});
|
||||
|
||||
it("does not stop an injected client", async () => {
|
||||
requestMock.mockClear().mockResolvedValue({ ok: true });
|
||||
stopMock.mockClear().mockResolvedValue(undefined);
|
||||
|
||||
await sendWithDefaults("chat_id:123", "hello");
|
||||
expect(stopMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ describe("ollama web search provider", () => {
|
|||
const webSearchProviders: unknown[] = [];
|
||||
|
||||
plugin.register({
|
||||
registerMemoryEmbeddingProvider() {},
|
||||
registerProvider() {},
|
||||
registerWebSearchProvider(provider: unknown) {
|
||||
webSearchProviders.push(provider);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { beforeAll, beforeEach, describe, it, vi } from "vitest";
|
||||
import { beforeEach, describe, it, vi } from "vitest";
|
||||
import {
|
||||
expectAugmentedCodexCatalog,
|
||||
expectCodexBuiltInSuppression,
|
||||
|
|
@ -38,46 +38,42 @@ vi.mock("../../../src/plugins/providers.runtime.js", () => ({
|
|||
}));
|
||||
|
||||
export function describeOpenAIProviderCatalogContract() {
|
||||
let augmentModelCatalogWithProviderPlugins: Awaited<
|
||||
ReturnType<typeof importProviderRuntimeCatalogModule>
|
||||
>["augmentModelCatalogWithProviderPlugins"];
|
||||
let resetProviderRuntimeHookCacheForTest: Awaited<
|
||||
ReturnType<typeof importProviderRuntimeCatalogModule>
|
||||
>["resetProviderRuntimeHookCacheForTest"];
|
||||
let resolveProviderBuiltInModelSuppression: Awaited<
|
||||
ReturnType<typeof importProviderRuntimeCatalogModule>
|
||||
>["resolveProviderBuiltInModelSuppression"];
|
||||
let openaiProviders: ProviderPlugin[];
|
||||
let openaiProvider: ProviderPlugin;
|
||||
const contractDepsPromise = (async () => {
|
||||
vi.resetModules();
|
||||
const openaiPlugin = loadBundledPluginPublicSurfaceSync<{
|
||||
default: Parameters<typeof registerProviderPlugin>[0]["plugin"];
|
||||
}>({
|
||||
pluginId: "openai",
|
||||
artifactBasename: "index.js",
|
||||
});
|
||||
const openaiProviders = (
|
||||
await registerProviderPlugin({
|
||||
plugin: openaiPlugin.default,
|
||||
id: "openai",
|
||||
name: "OpenAI",
|
||||
})
|
||||
).providers;
|
||||
const openaiProvider = requireRegisteredProvider(openaiProviders, "openai", "provider");
|
||||
const {
|
||||
augmentModelCatalogWithProviderPlugins,
|
||||
resetProviderRuntimeHookCacheForTest,
|
||||
resolveProviderBuiltInModelSuppression,
|
||||
} = await importProviderRuntimeCatalogModule();
|
||||
return {
|
||||
augmentModelCatalogWithProviderPlugins,
|
||||
resetProviderRuntimeHookCacheForTest,
|
||||
resolveProviderBuiltInModelSuppression,
|
||||
openaiProviders,
|
||||
openaiProvider,
|
||||
};
|
||||
})();
|
||||
|
||||
describe(
|
||||
"openai provider catalog contract",
|
||||
{ timeout: PROVIDER_CATALOG_CONTRACT_TIMEOUT_MS },
|
||||
() => {
|
||||
beforeAll(async () => {
|
||||
vi.resetModules();
|
||||
const openaiPlugin = loadBundledPluginPublicSurfaceSync<{
|
||||
default: Parameters<typeof registerProviderPlugin>[0]["plugin"];
|
||||
}>({
|
||||
pluginId: "openai",
|
||||
artifactBasename: "index.js",
|
||||
});
|
||||
openaiProviders = (
|
||||
await registerProviderPlugin({
|
||||
plugin: openaiPlugin.default,
|
||||
id: "openai",
|
||||
name: "OpenAI",
|
||||
})
|
||||
).providers;
|
||||
openaiProvider = requireRegisteredProvider(openaiProviders, "openai", "provider");
|
||||
({
|
||||
augmentModelCatalogWithProviderPlugins,
|
||||
resetProviderRuntimeHookCacheForTest,
|
||||
resolveProviderBuiltInModelSuppression,
|
||||
} = await importProviderRuntimeCatalogModule());
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
const { resetProviderRuntimeHookCacheForTest, openaiProviders } = await contractDepsPromise;
|
||||
resetProviderRuntimeHookCacheForTest();
|
||||
|
||||
resolvePluginProvidersMock.mockReset();
|
||||
|
|
@ -105,17 +101,20 @@ export function describeOpenAIProviderCatalogContract() {
|
|||
resolveCatalogHookProviderPluginIdsMock.mockReturnValue(["openai"]);
|
||||
});
|
||||
|
||||
it("keeps codex-only missing-auth hints wired through the provider runtime", () => {
|
||||
it("keeps codex-only missing-auth hints wired through the provider runtime", async () => {
|
||||
const { openaiProvider } = await contractDepsPromise;
|
||||
expectCodexMissingAuthHint(
|
||||
(params) => openaiProvider.buildMissingAuthMessage?.(params.context) ?? undefined,
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps built-in model suppression wired through the provider runtime", () => {
|
||||
it("keeps built-in model suppression wired through the provider runtime", async () => {
|
||||
const { resolveProviderBuiltInModelSuppression } = await contractDepsPromise;
|
||||
expectCodexBuiltInSuppression(resolveProviderBuiltInModelSuppression);
|
||||
});
|
||||
|
||||
it("keeps bundled model augmentation wired through the provider runtime", async () => {
|
||||
const { augmentModelCatalogWithProviderPlugins } = await contractDepsPromise;
|
||||
await expectAugmentedCodexCatalog(augmentModelCatalogWithProviderPlugins);
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import type { MsgContext } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { expectChannelInboundContextContract as expectInboundContextContract } from "../../../../src/channels/plugins/contracts/test-helpers.js";
|
||||
let createBaseSignalEventHandlerDeps: typeof import("./event-handler.test-harness.js").createBaseSignalEventHandlerDeps;
|
||||
let createSignalReceiveEvent: typeof import("./event-handler.test-harness.js").createSignalReceiveEvent;
|
||||
vi.useRealTimers();
|
||||
const [
|
||||
{ createBaseSignalEventHandlerDeps, createSignalReceiveEvent },
|
||||
{ createSignalEventHandler },
|
||||
] = await Promise.all([import("./event-handler.test-harness.js"), import("./event-handler.js")]);
|
||||
|
||||
const { sendTypingMock, sendReadReceiptMock, dispatchInboundMessageMock, capture } = vi.hoisted(
|
||||
() => {
|
||||
|
|
@ -48,16 +51,7 @@ vi.mock("../../../../src/pairing/pairing-store.js", () => ({
|
|||
upsertChannelPairingRequest: vi.fn(),
|
||||
}));
|
||||
|
||||
let createSignalEventHandler: typeof import("./event-handler.js").createSignalEventHandler;
|
||||
|
||||
describe("signal createSignalEventHandler inbound context", () => {
|
||||
beforeAll(async () => {
|
||||
vi.useRealTimers();
|
||||
({ createBaseSignalEventHandlerDeps, createSignalReceiveEvent } =
|
||||
await import("./event-handler.test-harness.js"));
|
||||
({ createSignalEventHandler } = await import("./event-handler.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
capture.ctx = undefined;
|
||||
sendTypingMock.mockReset().mockResolvedValue(true);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { MsgContext } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { buildDispatchInboundCaptureMock } from "../../../../src/channels/plugins/contracts/inbound-testkit.js";
|
||||
|
||||
type SignalMsgContext = Pick<MsgContext, "Body" | "WasMentioned"> & {
|
||||
|
|
@ -23,14 +23,15 @@ vi.mock("openclaw/plugin-sdk/reply-runtime", async () => {
|
|||
});
|
||||
});
|
||||
|
||||
let createBaseSignalEventHandlerDeps: typeof import("./event-handler.test-harness.js").createBaseSignalEventHandlerDeps;
|
||||
let createSignalReceiveEvent: typeof import("./event-handler.test-harness.js").createSignalReceiveEvent;
|
||||
let createSignalEventHandler: typeof import("./event-handler.js").createSignalEventHandler;
|
||||
let renderSignalMentions: typeof import("./mentions.js").renderSignalMentions;
|
||||
|
||||
beforeAll(async () => {
|
||||
({ renderSignalMentions } = await import("./mentions.js"));
|
||||
});
|
||||
const [
|
||||
{ createBaseSignalEventHandlerDeps, createSignalReceiveEvent },
|
||||
{ createSignalEventHandler },
|
||||
{ renderSignalMentions },
|
||||
] = await Promise.all([
|
||||
import("./event-handler.test-harness.js"),
|
||||
import("./event-handler.js"),
|
||||
import("./mentions.js"),
|
||||
]);
|
||||
|
||||
type GroupEventOpts = {
|
||||
message?: string;
|
||||
|
|
@ -106,12 +107,6 @@ async function expectSkippedGroupHistory(opts: GroupEventOpts, expectedBody: str
|
|||
}
|
||||
|
||||
describe("signal mention gating", () => {
|
||||
beforeAll(async () => {
|
||||
({ createBaseSignalEventHandlerDeps, createSignalReceiveEvent } =
|
||||
await import("./event-handler.test-harness.js"));
|
||||
({ createSignalEventHandler } = await import("./event-handler.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
capturedCtx = undefined;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { expectPairingReplyText } from "../../../test/helpers/pairing-reply.js";
|
||||
import {
|
||||
defaultSlackTestConfig,
|
||||
getSlackTestState,
|
||||
getSlackHandlerOrThrow,
|
||||
getSlackClient,
|
||||
getSlackHandlers,
|
||||
getSlackHandlerOrThrow,
|
||||
flush,
|
||||
resetSlackTestState,
|
||||
runSlackMessageOnce,
|
||||
|
|
@ -13,21 +13,21 @@ import {
|
|||
stopSlackMonitor,
|
||||
} from "./monitor.test-helpers.js";
|
||||
|
||||
let resetInboundDedupe: typeof import("openclaw/plugin-sdk/reply-runtime").resetInboundDedupe;
|
||||
let HISTORY_CONTEXT_MARKER: typeof import("../../../src/auto-reply/reply/history.js").HISTORY_CONTEXT_MARKER;
|
||||
let CURRENT_MESSAGE_MARKER: typeof import("../../../src/auto-reply/reply/mentions.js").CURRENT_MESSAGE_MARKER;
|
||||
let monitorSlackProvider: typeof import("./monitor.js").monitorSlackProvider;
|
||||
const [
|
||||
{ resetInboundDedupe },
|
||||
{ HISTORY_CONTEXT_MARKER },
|
||||
{ CURRENT_MESSAGE_MARKER },
|
||||
{ monitorSlackProvider },
|
||||
] = await Promise.all([
|
||||
import("openclaw/plugin-sdk/reply-runtime"),
|
||||
import("../../../src/auto-reply/reply/history.js"),
|
||||
import("../../../src/auto-reply/reply/mentions.js"),
|
||||
import("./monitor/provider.js"),
|
||||
]);
|
||||
|
||||
const slackTestState = getSlackTestState();
|
||||
const { sendMock, replyMock, reactMock, upsertPairingRequestMock } = slackTestState;
|
||||
|
||||
beforeAll(async () => {
|
||||
({ resetInboundDedupe } = await import("openclaw/plugin-sdk/reply-runtime"));
|
||||
({ HISTORY_CONTEXT_MARKER } = await import("../../../src/auto-reply/reply/history.js"));
|
||||
({ CURRENT_MESSAGE_MARKER } = await import("../../../src/auto-reply/reply/mentions.js"));
|
||||
({ monitorSlackProvider } = await import("./monitor.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
resetInboundDedupe();
|
||||
resetSlackTestState(defaultSlackTestConfig());
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const enqueueMock = vi.fn(async (_entry: unknown) => {});
|
||||
const flushKeyMock = vi.fn(async (_key: string) => {});
|
||||
const resolveThreadTsMock = vi.fn(async ({ message }: { message: Record<string, unknown> }) => ({
|
||||
...message,
|
||||
}));
|
||||
let createSlackMessageHandler: typeof import("./message-handler.js").createSlackMessageHandler;
|
||||
const { createSlackMessageHandler } = await import("./message-handler.js");
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/channel-inbound", async () => {
|
||||
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/channel-inbound")>(
|
||||
|
|
@ -72,10 +72,6 @@ async function handleDirectMessage(
|
|||
}
|
||||
|
||||
describe("createSlackMessageHandler", () => {
|
||||
beforeAll(async () => {
|
||||
({ createSlackMessageHandler } = await import("./message-handler.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
enqueueMock.mockClear();
|
||||
flushKeyMock.mockClear();
|
||||
|
|
|
|||
|
|
@ -3,30 +3,17 @@ import os from "node:os";
|
|||
import path from "node:path";
|
||||
import type { App } from "@slack/bolt";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { afterAll, describe, expect, it, vi } from "vitest";
|
||||
import type { SlackMessageEvent } from "../../types.js";
|
||||
|
||||
type PrepareSlackMessage = typeof import("./prepare.js").prepareSlackMessage;
|
||||
type CreateInboundSlackTestContext =
|
||||
typeof import("./prepare.test-helpers.js").createInboundSlackTestContext;
|
||||
type CreateSlackTestAccount = typeof import("./prepare.test-helpers.js").createSlackTestAccount;
|
||||
|
||||
let prepareSlackMessage: PrepareSlackMessage;
|
||||
let createInboundSlackTestContext: CreateInboundSlackTestContext;
|
||||
let createSlackTestAccount: CreateSlackTestAccount;
|
||||
let fixtureRoot = "";
|
||||
const [{ prepareSlackMessage }, helpers] = await Promise.all([
|
||||
import("./prepare.js"),
|
||||
import("./prepare.test-helpers.js"),
|
||||
]);
|
||||
const { createInboundSlackTestContext, createSlackTestAccount } = helpers;
|
||||
let fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-slack-room-thread-context-"));
|
||||
let caseId = 0;
|
||||
|
||||
async function loadSlackPrepareModules() {
|
||||
const [{ prepareSlackMessage: loadedPrepareSlackMessage }, helpers] = await Promise.all([
|
||||
import("./prepare.js"),
|
||||
import("./prepare.test-helpers.js"),
|
||||
]);
|
||||
prepareSlackMessage = loadedPrepareSlackMessage;
|
||||
createInboundSlackTestContext = helpers.createInboundSlackTestContext;
|
||||
createSlackTestAccount = helpers.createSlackTestAccount;
|
||||
}
|
||||
|
||||
function makeTmpStorePath() {
|
||||
if (!fixtureRoot) {
|
||||
throw new Error("fixtureRoot missing");
|
||||
|
|
@ -37,11 +24,6 @@ function makeTmpStorePath() {
|
|||
}
|
||||
|
||||
describe("prepareSlackMessage thread context allowlists", () => {
|
||||
beforeAll(async () => {
|
||||
await loadSlackPrepareModules();
|
||||
fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-slack-room-thread-context-"));
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
if (fixtureRoot) {
|
||||
fs.rmSync(fixtureRoot, { recursive: true, force: true });
|
||||
|
|
|
|||
|
|
@ -1,26 +1,13 @@
|
|||
import type { App } from "@slack/bolt";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { SlackMessageEvent } from "../../types.js";
|
||||
|
||||
type PrepareSlackMessage = typeof import("./prepare.js").prepareSlackMessage;
|
||||
type CreateInboundSlackTestContext =
|
||||
typeof import("./prepare.test-helpers.js").createInboundSlackTestContext;
|
||||
type CreateSlackTestAccount = typeof import("./prepare.test-helpers.js").createSlackTestAccount;
|
||||
|
||||
let prepareSlackMessage: PrepareSlackMessage;
|
||||
let createInboundSlackTestContext: CreateInboundSlackTestContext;
|
||||
let createSlackTestAccount: CreateSlackTestAccount;
|
||||
|
||||
async function loadSlackPrepareModules() {
|
||||
const [{ prepareSlackMessage: loadedPrepareSlackMessage }, helpers] = await Promise.all([
|
||||
import("./prepare.js"),
|
||||
import("./prepare.test-helpers.js"),
|
||||
]);
|
||||
prepareSlackMessage = loadedPrepareSlackMessage;
|
||||
createInboundSlackTestContext = helpers.createInboundSlackTestContext;
|
||||
createSlackTestAccount = helpers.createSlackTestAccount;
|
||||
}
|
||||
const [{ prepareSlackMessage }, helpers] = await Promise.all([
|
||||
import("./prepare.js"),
|
||||
import("./prepare.test-helpers.js"),
|
||||
]);
|
||||
const { createInboundSlackTestContext, createSlackTestAccount } = helpers;
|
||||
|
||||
function buildCtx(overrides?: { replyToMode?: "all" | "first" | "off" }) {
|
||||
const replyToMode = overrides?.replyToMode ?? "all";
|
||||
|
|
@ -48,10 +35,6 @@ function buildChannelMessage(overrides?: Partial<SlackMessageEvent>): SlackMessa
|
|||
}
|
||||
|
||||
describe("thread-level session keys", () => {
|
||||
beforeAll(async () => {
|
||||
await loadSlackPrepareModules();
|
||||
});
|
||||
|
||||
it("keeps top-level channel turns in one session when replyToMode=off", async () => {
|
||||
const ctx = buildCtx({ replyToMode: "off" });
|
||||
ctx.resolveUserName = async () => ({ name: "Alice" });
|
||||
|
|
|
|||
|
|
@ -180,16 +180,12 @@ vi.mock("./slash-commands.runtime.js", () => {
|
|||
});
|
||||
|
||||
type RegisterFn = (params: { ctx: unknown; account: unknown }) => Promise<void>;
|
||||
let registerSlackMonitorSlashCommands: RegisterFn;
|
||||
const { registerSlackMonitorSlashCommands } = (await import("./slash.js")) as {
|
||||
registerSlackMonitorSlashCommands: RegisterFn;
|
||||
};
|
||||
|
||||
const { dispatchMock } = getSlackSlashMocks();
|
||||
|
||||
beforeAll(async () => {
|
||||
({ registerSlackMonitorSlashCommands } = (await import("./slash.js")) as {
|
||||
registerSlackMonitorSlashCommands: RegisterFn;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
resetSlackSlashMocks();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
withBundledPluginEnablementCompat,
|
||||
withBundledPluginVitestCompat,
|
||||
} from "./bundled-compat.js";
|
||||
import { resolveBundledPluginRepoEntryPath } from "./bundled-plugin-metadata.js";
|
||||
import { createCapturedPluginRegistration } from "./captured-registration.js";
|
||||
import { discoverOpenClawPlugins } from "./discovery.js";
|
||||
import type { PluginLoadOptions } from "./loader.js";
|
||||
|
|
@ -213,6 +214,7 @@ export function loadBundledCapabilityRuntimeRegistry(params: {
|
|||
manifestRegistry.plugins.map((record) => [record.rootDir, record]),
|
||||
);
|
||||
const seenPluginIds = new Set<string>();
|
||||
const repoRoot = process.cwd();
|
||||
|
||||
for (const candidate of discovery.candidates) {
|
||||
const manifest = manifestByRoot.get(candidate.rootDir);
|
||||
|
|
@ -229,15 +231,22 @@ export function loadBundledCapabilityRuntimeRegistry(params: {
|
|||
name: manifest.name,
|
||||
description: manifest.description,
|
||||
version: manifest.version,
|
||||
source: candidate.source,
|
||||
source:
|
||||
env?.VITEST && params.pluginSdkResolution === "dist"
|
||||
? (resolveBundledPluginRepoEntryPath({
|
||||
rootDir: repoRoot,
|
||||
pluginId: manifest.id,
|
||||
preferBuilt: true,
|
||||
}) ?? candidate.source)
|
||||
: candidate.source,
|
||||
rootDir: candidate.rootDir,
|
||||
workspaceDir: candidate.workspaceDir,
|
||||
});
|
||||
|
||||
const opened = openBoundaryFileSync({
|
||||
absolutePath: candidate.source,
|
||||
rootPath: candidate.rootDir,
|
||||
boundaryLabel: "plugin root",
|
||||
absolutePath: record.source,
|
||||
rootPath: record.source === candidate.source ? candidate.rootDir : repoRoot,
|
||||
boundaryLabel: record.source === candidate.source ? "plugin root" : "repo root",
|
||||
rejectHardlinks: false,
|
||||
skipLexicalRootCheck: true,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
clearBundledPluginMetadataCache,
|
||||
listBundledPluginMetadata,
|
||||
resolveBundledPluginGeneratedPath,
|
||||
resolveBundledPluginRepoEntryPath,
|
||||
} from "./bundled-plugin-metadata.js";
|
||||
import {
|
||||
createGeneratedPluginTempRoot,
|
||||
|
|
@ -175,6 +176,45 @@ describe("bundled plugin metadata", () => {
|
|||
expectGeneratedPathResolution(tempRoot, path.join("plugin", "index.js"));
|
||||
});
|
||||
|
||||
it("resolves bundled repo entry paths from dist before workspace source", () => {
|
||||
const tempRoot = createGeneratedPluginTempRoot("openclaw-bundled-plugin-repo-entry-");
|
||||
const pluginRoot = path.join(tempRoot, "extensions", "alpha");
|
||||
const distPluginRoot = path.join(tempRoot, "dist", "extensions", "alpha");
|
||||
|
||||
writeJson(path.join(pluginRoot, "package.json"), {
|
||||
name: "@openclaw/alpha",
|
||||
version: "0.0.1",
|
||||
openclaw: {
|
||||
extensions: ["./index.ts"],
|
||||
},
|
||||
});
|
||||
writeJson(path.join(pluginRoot, "openclaw.plugin.json"), {
|
||||
id: "alpha",
|
||||
configSchema: { type: "object" },
|
||||
});
|
||||
fs.writeFileSync(path.join(pluginRoot, "index.ts"), "export const source = true;\n", "utf8");
|
||||
|
||||
expect(
|
||||
resolveBundledPluginRepoEntryPath({
|
||||
rootDir: tempRoot,
|
||||
pluginId: "alpha",
|
||||
preferBuilt: true,
|
||||
}),
|
||||
).toBe(path.join(pluginRoot, "index.ts"));
|
||||
|
||||
fs.mkdirSync(distPluginRoot, { recursive: true });
|
||||
fs.writeFileSync(path.join(distPluginRoot, "index.js"), "export const built = true;\n", "utf8");
|
||||
|
||||
clearBundledPluginMetadataCache();
|
||||
expect(
|
||||
resolveBundledPluginRepoEntryPath({
|
||||
rootDir: tempRoot,
|
||||
pluginId: "alpha",
|
||||
preferBuilt: true,
|
||||
}),
|
||||
).toBe(path.join(distPluginRoot, "index.js"));
|
||||
});
|
||||
|
||||
it("merges runtime channel schema metadata with manifest-owned channel config fields", () => {
|
||||
const tempRoot = createGeneratedPluginTempRoot("openclaw-bundled-plugin-channel-configs-");
|
||||
|
||||
|
|
|
|||
|
|
@ -243,3 +243,37 @@ export function resolveBundledPluginGeneratedPath(
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizeRelativePluginEntryPath(entryPath: string): string {
|
||||
return entryPath.replace(/^\.\//u, "");
|
||||
}
|
||||
|
||||
export function resolveBundledPluginRepoEntryPath(params: {
|
||||
rootDir: string;
|
||||
pluginId: string;
|
||||
preferBuilt?: boolean;
|
||||
}): string | null {
|
||||
const metadata = findBundledPluginMetadataById(params.pluginId, { rootDir: params.rootDir });
|
||||
if (!metadata) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const entryOrder = params.preferBuilt
|
||||
? [metadata.source.built, metadata.source.source]
|
||||
: [metadata.source.source, metadata.source.built];
|
||||
const baseDirs = [
|
||||
path.resolve(params.rootDir, "dist", "extensions", metadata.dirName),
|
||||
path.resolve(params.rootDir, "extensions", metadata.dirName),
|
||||
];
|
||||
|
||||
for (const baseDir of baseDirs) {
|
||||
for (const entryPath of entryOrder) {
|
||||
const candidate = path.resolve(baseDir, normalizeRelativePluginEntryPath(entryPath));
|
||||
if (fs.existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import type {
|
|||
WebFetchProviderPlugin,
|
||||
WebSearchProviderPlugin,
|
||||
} from "../types.js";
|
||||
import { BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS } from "./inventory/bundled-capability-metadata.js";
|
||||
import {
|
||||
loadVitestImageGenerationProviderContractRegistry,
|
||||
loadVitestMediaUnderstandingProviderContractRegistry,
|
||||
|
|
@ -91,6 +92,21 @@ function uniqueStrings(values: readonly string[]): string[] {
|
|||
}
|
||||
|
||||
function resolveBundledManifestContracts(): PluginRegistrationContractEntry[] {
|
||||
if (process.env.VITEST) {
|
||||
return BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.map((entry) => ({
|
||||
pluginId: entry.pluginId,
|
||||
providerIds: [...entry.providerIds],
|
||||
speechProviderIds: [...entry.speechProviderIds],
|
||||
realtimeTranscriptionProviderIds: [...entry.realtimeTranscriptionProviderIds],
|
||||
realtimeVoiceProviderIds: [...entry.realtimeVoiceProviderIds],
|
||||
mediaUnderstandingProviderIds: [...entry.mediaUnderstandingProviderIds],
|
||||
imageGenerationProviderIds: [...entry.imageGenerationProviderIds],
|
||||
videoGenerationProviderIds: [...entry.videoGenerationProviderIds],
|
||||
webFetchProviderIds: [...entry.webFetchProviderIds],
|
||||
webSearchProviderIds: [...entry.webSearchProviderIds],
|
||||
toolNames: [...entry.toolNames],
|
||||
}));
|
||||
}
|
||||
return loadPluginManifestRegistry({})
|
||||
.plugins.filter(
|
||||
(plugin) =>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import { createJiti } from "jiti";
|
||||
import { loadBundledCapabilityRuntimeRegistry } from "../bundled-capability-runtime.js";
|
||||
import { resolveManifestContractPluginIds } from "../manifest-registry.js";
|
||||
import { resolveBundledPluginRepoEntryPath } from "../bundled-plugin-metadata.js";
|
||||
import { createCapturedPluginRegistration } from "../captured-registration.js";
|
||||
import type { OpenClawPluginDefinition } from "../types.js";
|
||||
import type {
|
||||
ImageGenerationProviderPlugin,
|
||||
MediaUnderstandingProviderPlugin,
|
||||
|
|
@ -9,6 +12,7 @@ import type {
|
|||
SpeechProviderPlugin,
|
||||
VideoGenerationProviderPlugin,
|
||||
} from "../types.js";
|
||||
import { BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS } from "./inventory/bundled-capability-metadata.js";
|
||||
|
||||
export type SpeechProviderContractEntry = {
|
||||
pluginId: string;
|
||||
|
|
@ -54,6 +58,67 @@ type ManifestContractKey =
|
|||
| "videoGenerationProviders"
|
||||
| "musicGenerationProviders";
|
||||
|
||||
const VITEST_CONTRACT_PLUGIN_IDS = {
|
||||
imageGenerationProviders: BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.filter(
|
||||
(entry) => entry.imageGenerationProviderIds.length > 0,
|
||||
).map((entry) => entry.pluginId),
|
||||
speechProviders: BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.filter(
|
||||
(entry) => entry.speechProviderIds.length > 0,
|
||||
).map((entry) => entry.pluginId),
|
||||
mediaUnderstandingProviders: BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.filter(
|
||||
(entry) => entry.mediaUnderstandingProviderIds.length > 0,
|
||||
).map((entry) => entry.pluginId),
|
||||
realtimeVoiceProviders: BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.filter(
|
||||
(entry) => entry.realtimeVoiceProviderIds.length > 0,
|
||||
).map((entry) => entry.pluginId),
|
||||
realtimeTranscriptionProviders: BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.filter(
|
||||
(entry) => entry.realtimeTranscriptionProviderIds.length > 0,
|
||||
).map((entry) => entry.pluginId),
|
||||
videoGenerationProviders: BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.filter(
|
||||
(entry) => entry.videoGenerationProviderIds.length > 0,
|
||||
).map((entry) => entry.pluginId),
|
||||
} satisfies Record<ManifestContractKey, string[]>;
|
||||
|
||||
function loadVitestVideoGenerationFallbackEntries(
|
||||
pluginIds: readonly string[],
|
||||
): VideoGenerationProviderContractEntry[] {
|
||||
const jiti = createJiti(import.meta.url, {
|
||||
interopDefault: true,
|
||||
moduleCache: false,
|
||||
fsCache: false,
|
||||
});
|
||||
const repoRoot = process.cwd();
|
||||
return pluginIds.flatMap((pluginId) => {
|
||||
const modulePath = resolveBundledPluginRepoEntryPath({
|
||||
rootDir: repoRoot,
|
||||
pluginId,
|
||||
preferBuilt: true,
|
||||
});
|
||||
if (!modulePath) {
|
||||
return [];
|
||||
}
|
||||
try {
|
||||
const mod = jiti(modulePath) as
|
||||
| OpenClawPluginDefinition
|
||||
| { default?: OpenClawPluginDefinition };
|
||||
const plugin =
|
||||
(mod as { default?: OpenClawPluginDefinition }).default ??
|
||||
(mod as OpenClawPluginDefinition);
|
||||
if (typeof plugin?.register !== "function") {
|
||||
return [];
|
||||
}
|
||||
const captured = createCapturedPluginRegistration();
|
||||
void plugin.register(captured.api);
|
||||
return captured.videoGenerationProviders.map((provider) => ({
|
||||
pluginId,
|
||||
provider,
|
||||
}));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadVitestCapabilityContractEntries<T>(params: {
|
||||
contract: ManifestContractKey;
|
||||
pickEntries: (registry: ReturnType<typeof loadBundledCapabilityRuntimeRegistry>) => Array<{
|
||||
|
|
@ -61,19 +126,30 @@ function loadVitestCapabilityContractEntries<T>(params: {
|
|||
provider: T;
|
||||
}>;
|
||||
}): Array<{ pluginId: string; provider: T }> {
|
||||
const pluginIds = resolveManifestContractPluginIds({
|
||||
contract: params.contract,
|
||||
origin: "bundled",
|
||||
});
|
||||
const pluginIds = VITEST_CONTRACT_PLUGIN_IDS[params.contract];
|
||||
if (pluginIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
return params.pickEntries(
|
||||
const bulkEntries = params.pickEntries(
|
||||
loadBundledCapabilityRuntimeRegistry({
|
||||
pluginIds,
|
||||
pluginSdkResolution: "dist",
|
||||
}),
|
||||
);
|
||||
const coveredPluginIds = new Set(bulkEntries.map((entry) => entry.pluginId));
|
||||
if (coveredPluginIds.size === pluginIds.length) {
|
||||
return bulkEntries;
|
||||
}
|
||||
return pluginIds.flatMap((pluginId) =>
|
||||
params
|
||||
.pickEntries(
|
||||
loadBundledCapabilityRuntimeRegistry({
|
||||
pluginIds: [pluginId],
|
||||
pluginSdkResolution: "dist",
|
||||
}),
|
||||
)
|
||||
.filter((entry) => entry.pluginId === pluginId),
|
||||
);
|
||||
}
|
||||
|
||||
export function loadVitestSpeechProviderContractRegistry(): SpeechProviderContractEntry[] {
|
||||
|
|
@ -132,7 +208,7 @@ export function loadVitestImageGenerationProviderContractRegistry(): ImageGenera
|
|||
}
|
||||
|
||||
export function loadVitestVideoGenerationProviderContractRegistry(): VideoGenerationProviderContractEntry[] {
|
||||
return loadVitestCapabilityContractEntries({
|
||||
const entries = loadVitestCapabilityContractEntries({
|
||||
contract: "videoGenerationProviders",
|
||||
pickEntries: (registry) =>
|
||||
registry.videoGenerationProviders.map((entry) => ({
|
||||
|
|
@ -140,6 +216,14 @@ export function loadVitestVideoGenerationProviderContractRegistry(): VideoGenera
|
|||
provider: entry.provider,
|
||||
})),
|
||||
});
|
||||
const coveredPluginIds = new Set(entries.map((entry) => entry.pluginId));
|
||||
const missingPluginIds = VITEST_CONTRACT_PLUGIN_IDS.videoGenerationProviders.filter(
|
||||
(pluginId) => !coveredPluginIds.has(pluginId),
|
||||
);
|
||||
if (missingPluginIds.length === 0) {
|
||||
return entries;
|
||||
}
|
||||
return [...entries, ...loadVitestVideoGenerationFallbackEntries(missingPluginIds)];
|
||||
}
|
||||
|
||||
export function loadVitestMusicGenerationProviderContractRegistry(): MusicGenerationProviderContractEntry[] {
|
||||
|
|
|
|||
Loading…
Reference in New Issue