mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:10:44 +00:00
test: dedupe command fixtures
This commit is contained in:
@@ -136,6 +136,38 @@ function buildParams(overrides: Partial<ApplyAuthChoiceParams> = {}): ApplyAuthC
|
||||
};
|
||||
}
|
||||
|
||||
function buildLocalProviderInstallCatalogEntry() {
|
||||
return {
|
||||
pluginId: "local-provider-plugin",
|
||||
providerId: LOCAL_PROVIDER_ID,
|
||||
methodId: LOCAL_AUTH_METHOD_ID,
|
||||
choiceId: LOCAL_PROVIDER_ID,
|
||||
choiceLabel: LOCAL_PROVIDER_LABEL,
|
||||
label: LOCAL_PROVIDER_LABEL,
|
||||
origin: "bundled" as const,
|
||||
install: {
|
||||
npmSpec: "@openclaw/local-provider",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function buildInstalledLocalProviderPluginResult() {
|
||||
return {
|
||||
cfg: {
|
||||
plugins: {
|
||||
entries: {
|
||||
"local-provider-plugin": {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
installed: true,
|
||||
pluginId: "local-provider-plugin",
|
||||
status: "installed" as const,
|
||||
};
|
||||
}
|
||||
|
||||
describe("applyAuthChoiceLoadedPluginProvider", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
@@ -290,32 +322,8 @@ describe("applyAuthChoiceLoadedPluginProvider", () => {
|
||||
|
||||
it("installs a missing provider plugin and retries setup resolution", async () => {
|
||||
const provider = buildProvider();
|
||||
resolveProviderInstallCatalogEntry.mockReturnValue({
|
||||
pluginId: "local-provider-plugin",
|
||||
providerId: LOCAL_PROVIDER_ID,
|
||||
methodId: LOCAL_AUTH_METHOD_ID,
|
||||
choiceId: LOCAL_PROVIDER_ID,
|
||||
choiceLabel: LOCAL_PROVIDER_LABEL,
|
||||
label: LOCAL_PROVIDER_LABEL,
|
||||
origin: "bundled",
|
||||
install: {
|
||||
npmSpec: "@openclaw/local-provider",
|
||||
},
|
||||
});
|
||||
ensureOnboardingPluginInstalled.mockResolvedValue({
|
||||
cfg: {
|
||||
plugins: {
|
||||
entries: {
|
||||
"local-provider-plugin": {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
installed: true,
|
||||
pluginId: "local-provider-plugin",
|
||||
status: "installed",
|
||||
});
|
||||
resolveProviderInstallCatalogEntry.mockReturnValue(buildLocalProviderInstallCatalogEntry());
|
||||
ensureOnboardingPluginInstalled.mockResolvedValue(buildInstalledLocalProviderPluginResult());
|
||||
resolvePluginProviders.mockReturnValue([provider]);
|
||||
resolveProviderPluginChoice.mockReturnValueOnce(null).mockReturnValueOnce({
|
||||
provider,
|
||||
@@ -341,18 +349,7 @@ describe("applyAuthChoiceLoadedPluginProvider", () => {
|
||||
});
|
||||
|
||||
it("does not persist plugin enablement when install is skipped", async () => {
|
||||
resolveProviderInstallCatalogEntry.mockReturnValue({
|
||||
pluginId: "local-provider-plugin",
|
||||
providerId: LOCAL_PROVIDER_ID,
|
||||
methodId: LOCAL_AUTH_METHOD_ID,
|
||||
choiceId: LOCAL_PROVIDER_ID,
|
||||
choiceLabel: LOCAL_PROVIDER_LABEL,
|
||||
label: LOCAL_PROVIDER_LABEL,
|
||||
origin: "bundled",
|
||||
install: {
|
||||
npmSpec: "@openclaw/local-provider",
|
||||
},
|
||||
});
|
||||
resolveProviderInstallCatalogEntry.mockReturnValue(buildLocalProviderInstallCatalogEntry());
|
||||
resolveProviderPluginChoice.mockReturnValue(null);
|
||||
|
||||
const result = await applyAuthChoiceLoadedPluginProvider(buildParams());
|
||||
@@ -362,32 +359,8 @@ describe("applyAuthChoiceLoadedPluginProvider", () => {
|
||||
});
|
||||
|
||||
it("preserves install config when the chosen provider still cannot resolve after install", async () => {
|
||||
resolveProviderInstallCatalogEntry.mockReturnValue({
|
||||
pluginId: "local-provider-plugin",
|
||||
providerId: LOCAL_PROVIDER_ID,
|
||||
methodId: LOCAL_AUTH_METHOD_ID,
|
||||
choiceId: LOCAL_PROVIDER_ID,
|
||||
choiceLabel: LOCAL_PROVIDER_LABEL,
|
||||
label: LOCAL_PROVIDER_LABEL,
|
||||
origin: "bundled",
|
||||
install: {
|
||||
npmSpec: "@openclaw/local-provider",
|
||||
},
|
||||
});
|
||||
ensureOnboardingPluginInstalled.mockResolvedValue({
|
||||
cfg: {
|
||||
plugins: {
|
||||
entries: {
|
||||
"local-provider-plugin": {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
installed: true,
|
||||
pluginId: "local-provider-plugin",
|
||||
status: "installed",
|
||||
});
|
||||
resolveProviderInstallCatalogEntry.mockReturnValue(buildLocalProviderInstallCatalogEntry());
|
||||
ensureOnboardingPluginInstalled.mockResolvedValue(buildInstalledLocalProviderPluginResult());
|
||||
resolveProviderPluginChoice.mockReturnValue(null);
|
||||
|
||||
const result = await applyAuthChoiceLoadedPluginProvider(buildParams());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js";
|
||||
import { channelsStatusCommand } from "./channels/status.js";
|
||||
import { createCapturingTestRuntime } from "./test-runtime-config-helpers.js";
|
||||
|
||||
const resolveDefaultAccountId = () => DEFAULT_ACCOUNT_ID;
|
||||
|
||||
@@ -176,17 +177,6 @@ function createTokenOnlyPlugin() {
|
||||
};
|
||||
}
|
||||
|
||||
function createRuntimeCapture() {
|
||||
const logs: string[] = [];
|
||||
const errors: string[] = [];
|
||||
const runtime = {
|
||||
log: (message: unknown) => logs.push(String(message)),
|
||||
error: (message: unknown) => errors.push(String(message)),
|
||||
exit: (_code?: number) => undefined,
|
||||
};
|
||||
return { runtime, logs, errors };
|
||||
}
|
||||
|
||||
describe("channelsStatusCommand SecretRef fallback flow", () => {
|
||||
beforeEach(() => {
|
||||
mocks.callGateway.mockReset();
|
||||
@@ -210,7 +200,7 @@ describe("channelsStatusCommand SecretRef fallback flow", () => {
|
||||
"channels status: channels.discord.token is unavailable in this command path; continuing with degraded read-only config.",
|
||||
],
|
||||
});
|
||||
const { runtime, logs, errors } = createRuntimeCapture();
|
||||
const { runtime, logs, errors } = createCapturingTestRuntime();
|
||||
|
||||
await channelsStatusCommand({ probe: false }, runtime as never);
|
||||
|
||||
@@ -239,7 +229,7 @@ describe("channelsStatusCommand SecretRef fallback flow", () => {
|
||||
effectiveConfig: { secretResolved: true, channels: {} },
|
||||
diagnostics: [],
|
||||
});
|
||||
const { runtime, logs } = createRuntimeCapture();
|
||||
const { runtime, logs } = createCapturingTestRuntime();
|
||||
|
||||
await channelsStatusCommand({ probe: false }, runtime as never);
|
||||
|
||||
@@ -267,7 +257,7 @@ describe("channelsStatusCommand SecretRef fallback flow", () => {
|
||||
effectiveConfig: { secretResolved: true, channels: {} },
|
||||
diagnostics: [],
|
||||
});
|
||||
const { runtime, logs, errors } = createRuntimeCapture();
|
||||
const { runtime, logs, errors } = createCapturingTestRuntime();
|
||||
|
||||
await channelsStatusCommand({ json: true, probe: false }, runtime as never);
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "../plugins/loader.test-fixtures.js";
|
||||
import { withEnvAsync } from "../test-utils/env.js";
|
||||
import { channelsStatusCommand } from "./channels/status.js";
|
||||
import { createCapturingTestRuntime } from "./test-runtime-config-helpers.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
callGateway: vi.fn(),
|
||||
@@ -89,17 +90,6 @@ function writeExternalEnvChannelPlugin() {
|
||||
return { pluginDir, fullMarker };
|
||||
}
|
||||
|
||||
function createRuntimeCapture() {
|
||||
const logs: string[] = [];
|
||||
const errors: string[] = [];
|
||||
const runtime = {
|
||||
log: (message: unknown) => logs.push(String(message)),
|
||||
error: (message: unknown) => errors.push(String(message)),
|
||||
exit: (_code?: number) => undefined,
|
||||
};
|
||||
return { runtime, logs, errors };
|
||||
}
|
||||
|
||||
describe("channelsStatusCommand external env-only channel fallback", () => {
|
||||
beforeEach(() => {
|
||||
mocks.callGateway.mockReset();
|
||||
@@ -127,7 +117,7 @@ describe("channelsStatusCommand external env-only channel fallback", () => {
|
||||
effectiveConfig: config,
|
||||
diagnostics: [],
|
||||
});
|
||||
const { runtime, logs } = createRuntimeCapture();
|
||||
const { runtime, logs } = createCapturingTestRuntime();
|
||||
|
||||
await withEnvAsync({ EXTERNAL_ENV_CHANNEL_TOKEN: "token" }, async () => {
|
||||
await channelsStatusCommand({ json: true, probe: false }, runtime as never);
|
||||
|
||||
@@ -9,6 +9,12 @@ import {
|
||||
import { maybeRepairBundledPluginRuntimeDeps } from "./doctor-bundled-plugin-runtime-deps.js";
|
||||
import type { DoctorPrompter } from "./doctor-prompter.js";
|
||||
|
||||
type InstalledRuntimeDeps = Array<{
|
||||
installRoot: string;
|
||||
missingSpecs: string[];
|
||||
installSpecs: string[];
|
||||
}>;
|
||||
|
||||
function writeJson(filePath: string, value: unknown) {
|
||||
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
||||
@@ -25,6 +31,31 @@ function writeBundledChannelPlugin(root: string, id: string, dependencies: Recor
|
||||
});
|
||||
}
|
||||
|
||||
function createInstalledRuntimeDeps(): InstalledRuntimeDeps {
|
||||
return [];
|
||||
}
|
||||
|
||||
function createNonInteractivePrompter(
|
||||
options: { updateInProgress?: boolean } = {},
|
||||
): DoctorPrompter {
|
||||
return {
|
||||
shouldRepair: false,
|
||||
shouldForce: false,
|
||||
repairMode: {
|
||||
shouldRepair: false,
|
||||
shouldForce: false,
|
||||
nonInteractive: true,
|
||||
canPrompt: false,
|
||||
updateInProgress: options.updateInProgress ?? false,
|
||||
},
|
||||
confirm: async () => false,
|
||||
confirmAutoFix: async () => false,
|
||||
confirmAggressiveAutoFix: async () => false,
|
||||
confirmRuntimeRepair: async () => false,
|
||||
select: async (_params: unknown, fallback: unknown) => fallback,
|
||||
} as DoctorPrompter;
|
||||
}
|
||||
|
||||
describe("doctor bundled plugin runtime deps", () => {
|
||||
it("skips source checkouts", () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-doctor-bundled-"));
|
||||
@@ -175,31 +206,11 @@ describe("doctor bundled plugin runtime deps", () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-doctor-bundled-"));
|
||||
writeJson(path.join(root, "package.json"), { name: "openclaw" });
|
||||
writeBundledChannelPlugin(root, "telegram", { grammy: "1.37.0" });
|
||||
const installed: Array<{
|
||||
installRoot: string;
|
||||
missingSpecs: string[];
|
||||
installSpecs: string[];
|
||||
}> = [];
|
||||
const prompter = {
|
||||
shouldRepair: false,
|
||||
shouldForce: false,
|
||||
repairMode: {
|
||||
shouldRepair: false,
|
||||
shouldForce: false,
|
||||
nonInteractive: true,
|
||||
canPrompt: false,
|
||||
updateInProgress: false,
|
||||
},
|
||||
confirm: async () => false,
|
||||
confirmAutoFix: async () => false,
|
||||
confirmAggressiveAutoFix: async () => false,
|
||||
confirmRuntimeRepair: async () => false,
|
||||
select: async (_params: unknown, fallback: unknown) => fallback,
|
||||
} as DoctorPrompter;
|
||||
const installed = createInstalledRuntimeDeps();
|
||||
|
||||
await maybeRepairBundledPluginRuntimeDeps({
|
||||
runtime: { error: () => {} } as never,
|
||||
prompter,
|
||||
prompter: createNonInteractivePrompter(),
|
||||
packageRoot: root,
|
||||
config: {
|
||||
plugins: { enabled: true },
|
||||
@@ -223,31 +234,11 @@ describe("doctor bundled plugin runtime deps", () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-doctor-bundled-"));
|
||||
writeJson(path.join(root, "package.json"), { name: "openclaw" });
|
||||
writeBundledChannelPlugin(root, "feishu", { "@larksuiteoapi/node-sdk": "^1.61.0" });
|
||||
const installed: Array<{
|
||||
installRoot: string;
|
||||
missingSpecs: string[];
|
||||
installSpecs: string[];
|
||||
}> = [];
|
||||
const prompter = {
|
||||
shouldRepair: false,
|
||||
shouldForce: false,
|
||||
repairMode: {
|
||||
shouldRepair: false,
|
||||
shouldForce: false,
|
||||
nonInteractive: true,
|
||||
canPrompt: false,
|
||||
updateInProgress: true,
|
||||
},
|
||||
confirm: async () => false,
|
||||
confirmAutoFix: async () => false,
|
||||
confirmAggressiveAutoFix: async () => false,
|
||||
confirmRuntimeRepair: async () => false,
|
||||
select: async (_params: unknown, fallback: unknown) => fallback,
|
||||
} as DoctorPrompter;
|
||||
const installed = createInstalledRuntimeDeps();
|
||||
|
||||
await maybeRepairBundledPluginRuntimeDeps({
|
||||
runtime: { error: () => {} } as never,
|
||||
prompter,
|
||||
prompter: createNonInteractivePrompter({ updateInProgress: true }),
|
||||
packageRoot: root,
|
||||
includeConfiguredChannels: true,
|
||||
config: {
|
||||
@@ -274,31 +265,11 @@ describe("doctor bundled plugin runtime deps", () => {
|
||||
writeJson(path.join(root, "package.json"), { name: "openclaw", version: "2026.4.22" });
|
||||
writeBundledChannelPlugin(root, "slack", { "@slack/web-api": "7.15.1" });
|
||||
const env = { OPENCLAW_PLUGIN_STAGE_DIR: stageDir };
|
||||
const installed: Array<{
|
||||
installRoot: string;
|
||||
missingSpecs: string[];
|
||||
installSpecs: string[];
|
||||
}> = [];
|
||||
const prompter = {
|
||||
shouldRepair: false,
|
||||
shouldForce: false,
|
||||
repairMode: {
|
||||
shouldRepair: false,
|
||||
shouldForce: false,
|
||||
nonInteractive: true,
|
||||
canPrompt: false,
|
||||
updateInProgress: false,
|
||||
},
|
||||
confirm: async () => false,
|
||||
confirmAutoFix: async () => false,
|
||||
confirmAggressiveAutoFix: async () => false,
|
||||
confirmRuntimeRepair: async () => false,
|
||||
select: async (_params: unknown, fallback: unknown) => fallback,
|
||||
} as DoctorPrompter;
|
||||
const installed = createInstalledRuntimeDeps();
|
||||
|
||||
await maybeRepairBundledPluginRuntimeDeps({
|
||||
runtime: { error: () => {} } as never,
|
||||
prompter,
|
||||
prompter: createNonInteractivePrompter(),
|
||||
env,
|
||||
packageRoot: root,
|
||||
config: {
|
||||
@@ -330,31 +301,11 @@ describe("doctor bundled plugin runtime deps", () => {
|
||||
name: "@slack/web-api",
|
||||
version: "7.15.1",
|
||||
});
|
||||
const installed: Array<{
|
||||
installRoot: string;
|
||||
missingSpecs: string[];
|
||||
installSpecs: string[];
|
||||
}> = [];
|
||||
const prompter = {
|
||||
shouldRepair: false,
|
||||
shouldForce: false,
|
||||
repairMode: {
|
||||
shouldRepair: false,
|
||||
shouldForce: false,
|
||||
nonInteractive: true,
|
||||
canPrompt: false,
|
||||
updateInProgress: false,
|
||||
},
|
||||
confirm: async () => false,
|
||||
confirmAutoFix: async () => false,
|
||||
confirmAggressiveAutoFix: async () => false,
|
||||
confirmRuntimeRepair: async () => false,
|
||||
select: async (_params: unknown, fallback: unknown) => fallback,
|
||||
} as DoctorPrompter;
|
||||
const installed = createInstalledRuntimeDeps();
|
||||
|
||||
await maybeRepairBundledPluginRuntimeDeps({
|
||||
runtime: { error: () => {} } as never,
|
||||
prompter,
|
||||
prompter: createNonInteractivePrompter(),
|
||||
packageRoot: root,
|
||||
includeConfiguredChannels: true,
|
||||
config: {
|
||||
|
||||
@@ -32,6 +32,80 @@ vi.mock("../../../channels/plugins/read-only.js", () => ({
|
||||
) => mocks.resolveReadOnlyChannelPluginsForConfig(...args),
|
||||
}));
|
||||
|
||||
function createMatrixEnabledConfig() {
|
||||
return {
|
||||
channels: {
|
||||
matrix: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createNormalizeCompatibilityConfig(change = "matrix") {
|
||||
return vi.fn(({ cfg }: { cfg: unknown }) => ({
|
||||
config: cfg,
|
||||
changes: [change],
|
||||
}));
|
||||
}
|
||||
|
||||
function mockReadOnlyMatrixPlugin(doctor?: Record<string, unknown>) {
|
||||
mocks.resolveReadOnlyChannelPluginsForConfig.mockReturnValue({
|
||||
plugins: [
|
||||
{
|
||||
id: "matrix",
|
||||
...(doctor ? { doctor } : {}),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
function mockBundledMatrixSetupPlugin(doctor?: Record<string, unknown>) {
|
||||
mocks.getBundledChannelSetupPlugin.mockImplementation((id: string) =>
|
||||
id === "matrix"
|
||||
? {
|
||||
id: "matrix",
|
||||
...(doctor ? { doctor } : {}),
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
}
|
||||
|
||||
function mockBundledMatrixRuntimePlugin(doctor?: Record<string, unknown>) {
|
||||
mocks.getBundledChannelPlugin.mockImplementation((id: string) =>
|
||||
id === "matrix"
|
||||
? {
|
||||
id: "matrix",
|
||||
...(doctor ? { doctor } : {}),
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
}
|
||||
|
||||
function expectMatrixDoctorLookupCalls(cfg?: unknown) {
|
||||
if (cfg) {
|
||||
expect(mocks.resolveReadOnlyChannelPluginsForConfig).toHaveBeenCalledWith(cfg, {
|
||||
includePersistedAuthState: false,
|
||||
});
|
||||
}
|
||||
expect(mocks.getLoadedChannelPlugin).toHaveBeenCalledWith("matrix");
|
||||
expect(mocks.getBundledChannelSetupPlugin).toHaveBeenCalledWith("matrix");
|
||||
expect(mocks.getBundledChannelPlugin).toHaveBeenCalledWith("matrix");
|
||||
}
|
||||
|
||||
async function expectRuntimeWarningFallback(params: {
|
||||
cfg: unknown;
|
||||
normalizeCompatibilityConfig: ReturnType<typeof vi.fn>;
|
||||
collectMutableAllowlistWarnings: ReturnType<typeof vi.fn>;
|
||||
}) {
|
||||
expect(collectChannelDoctorCompatibilityMutations(params.cfg as never)).toHaveLength(1);
|
||||
await expect(
|
||||
collectChannelDoctorMutableAllowlistWarnings({ cfg: params.cfg as never }),
|
||||
).resolves.toEqual(["runtime warning"]);
|
||||
expect(params.normalizeCompatibilityConfig).toHaveBeenCalledTimes(1);
|
||||
expect(params.collectMutableAllowlistWarnings).toHaveBeenCalledTimes(1);
|
||||
}
|
||||
|
||||
describe("channel doctor compatibility mutations", () => {
|
||||
beforeEach(() => {
|
||||
mocks.getLoadedChannelPlugin.mockReset();
|
||||
@@ -84,224 +158,80 @@ describe("channel doctor compatibility mutations", () => {
|
||||
});
|
||||
|
||||
it("uses read-only doctor adapters for configured channel ids", () => {
|
||||
const normalizeCompatibilityConfig = vi.fn(({ cfg }: { cfg: unknown }) => ({
|
||||
config: cfg,
|
||||
changes: ["matrix"],
|
||||
}));
|
||||
mocks.resolveReadOnlyChannelPluginsForConfig.mockReturnValue({
|
||||
plugins: [
|
||||
{
|
||||
id: "matrix",
|
||||
doctor: { normalizeCompatibilityConfig },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const cfg = {
|
||||
channels: {
|
||||
matrix: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
const normalizeCompatibilityConfig = createNormalizeCompatibilityConfig();
|
||||
mockReadOnlyMatrixPlugin({ normalizeCompatibilityConfig });
|
||||
const cfg = createMatrixEnabledConfig();
|
||||
|
||||
const result = collectChannelDoctorCompatibilityMutations(cfg as never);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(normalizeCompatibilityConfig).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.resolveReadOnlyChannelPluginsForConfig).toHaveBeenCalledWith(cfg, {
|
||||
includePersistedAuthState: false,
|
||||
});
|
||||
expect(mocks.getLoadedChannelPlugin).toHaveBeenCalledWith("matrix");
|
||||
expect(mocks.getBundledChannelSetupPlugin).toHaveBeenCalledWith("matrix");
|
||||
expect(mocks.getBundledChannelPlugin).toHaveBeenCalledWith("matrix");
|
||||
expectMatrixDoctorLookupCalls(cfg);
|
||||
expect(mocks.getBundledChannelSetupPlugin).not.toHaveBeenCalledWith("discord");
|
||||
});
|
||||
|
||||
it("merges partial doctor adapters instead of masking runtime-only hooks", async () => {
|
||||
const normalizeCompatibilityConfig = vi.fn(({ cfg }: { cfg: unknown }) => ({
|
||||
config: cfg,
|
||||
changes: ["matrix"],
|
||||
}));
|
||||
const normalizeCompatibilityConfig = createNormalizeCompatibilityConfig();
|
||||
const collectMutableAllowlistWarnings = vi.fn(() => ["runtime warning"]);
|
||||
mocks.resolveReadOnlyChannelPluginsForConfig.mockReturnValue({
|
||||
plugins: [
|
||||
{
|
||||
id: "matrix",
|
||||
doctor: { normalizeCompatibilityConfig },
|
||||
},
|
||||
],
|
||||
mockReadOnlyMatrixPlugin({ normalizeCompatibilityConfig });
|
||||
mockBundledMatrixRuntimePlugin({ collectMutableAllowlistWarnings });
|
||||
const cfg = createMatrixEnabledConfig();
|
||||
|
||||
await expectRuntimeWarningFallback({
|
||||
cfg,
|
||||
normalizeCompatibilityConfig,
|
||||
collectMutableAllowlistWarnings,
|
||||
});
|
||||
mocks.getBundledChannelPlugin.mockImplementation((id: string) =>
|
||||
id === "matrix"
|
||||
? {
|
||||
id: "matrix",
|
||||
doctor: { collectMutableAllowlistWarnings },
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
|
||||
const cfg = {
|
||||
channels: {
|
||||
matrix: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(collectChannelDoctorCompatibilityMutations(cfg as never)).toHaveLength(1);
|
||||
await expect(
|
||||
collectChannelDoctorMutableAllowlistWarnings({ cfg: cfg as never }),
|
||||
).resolves.toEqual(["runtime warning"]);
|
||||
expect(normalizeCompatibilityConfig).toHaveBeenCalledTimes(1);
|
||||
expect(collectMutableAllowlistWarnings).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("ignores malformed doctor adapter values so valid fallbacks still run", async () => {
|
||||
const normalizeCompatibilityConfig = vi.fn(({ cfg }: { cfg: unknown }) => ({
|
||||
config: cfg,
|
||||
changes: ["setup"],
|
||||
}));
|
||||
const normalizeCompatibilityConfig = createNormalizeCompatibilityConfig("setup");
|
||||
const collectMutableAllowlistWarnings = vi.fn(() => ["runtime warning"]);
|
||||
mocks.resolveReadOnlyChannelPluginsForConfig.mockReturnValue({
|
||||
plugins: [
|
||||
{
|
||||
id: "matrix",
|
||||
doctor: {
|
||||
normalizeCompatibilityConfig: null,
|
||||
collectMutableAllowlistWarnings: "not-a-function",
|
||||
warnOnEmptyGroupSenderAllowlist: "yes",
|
||||
},
|
||||
},
|
||||
],
|
||||
mockReadOnlyMatrixPlugin({
|
||||
normalizeCompatibilityConfig: null,
|
||||
collectMutableAllowlistWarnings: "not-a-function",
|
||||
warnOnEmptyGroupSenderAllowlist: "yes",
|
||||
});
|
||||
mocks.getBundledChannelSetupPlugin.mockImplementation((id: string) =>
|
||||
id === "matrix"
|
||||
? {
|
||||
id: "matrix",
|
||||
doctor: { normalizeCompatibilityConfig },
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
mocks.getBundledChannelPlugin.mockImplementation((id: string) =>
|
||||
id === "matrix"
|
||||
? {
|
||||
id: "matrix",
|
||||
doctor: { collectMutableAllowlistWarnings },
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
mockBundledMatrixSetupPlugin({ normalizeCompatibilityConfig });
|
||||
mockBundledMatrixRuntimePlugin({ collectMutableAllowlistWarnings });
|
||||
const cfg = createMatrixEnabledConfig();
|
||||
|
||||
const cfg = {
|
||||
channels: {
|
||||
matrix: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(collectChannelDoctorCompatibilityMutations(cfg as never)).toHaveLength(1);
|
||||
await expect(
|
||||
collectChannelDoctorMutableAllowlistWarnings({ cfg: cfg as never }),
|
||||
).resolves.toEqual(["runtime warning"]);
|
||||
expect(normalizeCompatibilityConfig).toHaveBeenCalledTimes(1);
|
||||
expect(collectMutableAllowlistWarnings).toHaveBeenCalledTimes(1);
|
||||
await expectRuntimeWarningFallback({
|
||||
cfg,
|
||||
normalizeCompatibilityConfig,
|
||||
collectMutableAllowlistWarnings,
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to setup doctor adapters when read-only plugins lack doctor hooks", () => {
|
||||
const normalizeCompatibilityConfig = vi.fn(({ cfg }: { cfg: unknown }) => ({
|
||||
config: cfg,
|
||||
changes: ["matrix"],
|
||||
}));
|
||||
mocks.resolveReadOnlyChannelPluginsForConfig.mockReturnValue({
|
||||
plugins: [
|
||||
{
|
||||
id: "matrix",
|
||||
},
|
||||
],
|
||||
});
|
||||
mocks.getBundledChannelSetupPlugin.mockImplementation((id: string) =>
|
||||
id === "matrix"
|
||||
? {
|
||||
id: "matrix",
|
||||
doctor: { normalizeCompatibilityConfig },
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
|
||||
const cfg = {
|
||||
channels: {
|
||||
matrix: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
const normalizeCompatibilityConfig = createNormalizeCompatibilityConfig();
|
||||
mockReadOnlyMatrixPlugin();
|
||||
mockBundledMatrixSetupPlugin({ normalizeCompatibilityConfig });
|
||||
const cfg = createMatrixEnabledConfig();
|
||||
|
||||
const result = collectChannelDoctorCompatibilityMutations(cfg as never);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(normalizeCompatibilityConfig).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.resolveReadOnlyChannelPluginsForConfig).toHaveBeenCalledWith(cfg, {
|
||||
includePersistedAuthState: false,
|
||||
});
|
||||
expect(mocks.getLoadedChannelPlugin).toHaveBeenCalledWith("matrix");
|
||||
expect(mocks.getBundledChannelSetupPlugin).toHaveBeenCalledWith("matrix");
|
||||
expect(mocks.getBundledChannelPlugin).toHaveBeenCalledWith("matrix");
|
||||
expectMatrixDoctorLookupCalls(cfg);
|
||||
});
|
||||
|
||||
it("falls back to bundled runtime doctor adapters when setup adapters lack doctor hooks", () => {
|
||||
const normalizeCompatibilityConfig = vi.fn(({ cfg }: { cfg: unknown }) => ({
|
||||
config: cfg,
|
||||
changes: ["matrix"],
|
||||
}));
|
||||
mocks.resolveReadOnlyChannelPluginsForConfig.mockReturnValue({
|
||||
plugins: [
|
||||
{
|
||||
id: "matrix",
|
||||
},
|
||||
],
|
||||
});
|
||||
mocks.getBundledChannelSetupPlugin.mockImplementation((id: string) =>
|
||||
id === "matrix"
|
||||
? {
|
||||
id: "matrix",
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
mocks.getBundledChannelPlugin.mockImplementation((id: string) =>
|
||||
id === "matrix"
|
||||
? {
|
||||
id: "matrix",
|
||||
doctor: { normalizeCompatibilityConfig },
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
|
||||
const cfg = {
|
||||
channels: {
|
||||
matrix: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
const normalizeCompatibilityConfig = createNormalizeCompatibilityConfig();
|
||||
mockReadOnlyMatrixPlugin();
|
||||
mockBundledMatrixSetupPlugin();
|
||||
mockBundledMatrixRuntimePlugin({ normalizeCompatibilityConfig });
|
||||
const cfg = createMatrixEnabledConfig();
|
||||
|
||||
const result = collectChannelDoctorCompatibilityMutations(cfg as never);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(normalizeCompatibilityConfig).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.getLoadedChannelPlugin).toHaveBeenCalledWith("matrix");
|
||||
expect(mocks.getBundledChannelSetupPlugin).toHaveBeenCalledWith("matrix");
|
||||
expect(mocks.getBundledChannelPlugin).toHaveBeenCalledWith("matrix");
|
||||
expectMatrixDoctorLookupCalls();
|
||||
});
|
||||
|
||||
it("passes explicit env into read-only channel plugin discovery", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
matrix: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
const cfg = createMatrixEnabledConfig();
|
||||
const env = { OPENCLAW_HOME: "/tmp/openclaw-test-home" };
|
||||
|
||||
collectChannelDoctorCompatibilityMutations(cfg as never, { env });
|
||||
|
||||
Reference in New Issue
Block a user