mirror of https://github.com/openclaw/openclaw.git
test: dedupe plugin manifest and wizard suites
This commit is contained in:
parent
fad42b19ee
commit
c364fc8428
|
|
@ -22,6 +22,15 @@ function makeTempDir() {
|
|||
|
||||
const mkdirSafe = mkdirSafeDir;
|
||||
|
||||
function expectLoadedManifest(rootDir: string, bundleFormat: "codex" | "claude" | "cursor") {
|
||||
const result = loadBundleManifest({ rootDir, bundleFormat });
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) {
|
||||
throw new Error("expected bundle manifest to load");
|
||||
}
|
||||
return result.manifest;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
cleanupTrackedTempDirs(tempDirs);
|
||||
});
|
||||
|
|
@ -55,12 +64,7 @@ describe("bundle manifest parsing", () => {
|
|||
);
|
||||
|
||||
expect(detectBundleManifestFormat(rootDir)).toBe("codex");
|
||||
const result = loadBundleManifest({ rootDir, bundleFormat: "codex" });
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) {
|
||||
return;
|
||||
}
|
||||
expect(result.manifest).toMatchObject({
|
||||
expect(expectLoadedManifest(rootDir, "codex")).toMatchObject({
|
||||
id: "sample-bundle",
|
||||
name: "Sample Bundle",
|
||||
description: "Codex fixture",
|
||||
|
|
@ -101,12 +105,7 @@ describe("bundle manifest parsing", () => {
|
|||
);
|
||||
|
||||
expect(detectBundleManifestFormat(rootDir)).toBe("claude");
|
||||
const result = loadBundleManifest({ rootDir, bundleFormat: "claude" });
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) {
|
||||
return;
|
||||
}
|
||||
expect(result.manifest).toMatchObject({
|
||||
expect(expectLoadedManifest(rootDir, "claude")).toMatchObject({
|
||||
id: "claude-sample",
|
||||
name: "Claude Sample",
|
||||
description: "Claude fixture",
|
||||
|
|
@ -147,12 +146,7 @@ describe("bundle manifest parsing", () => {
|
|||
fs.writeFileSync(path.join(rootDir, ".mcp.json"), '{"servers":{}}', "utf-8");
|
||||
|
||||
expect(detectBundleManifestFormat(rootDir)).toBe("cursor");
|
||||
const result = loadBundleManifest({ rootDir, bundleFormat: "cursor" });
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) {
|
||||
return;
|
||||
}
|
||||
expect(result.manifest).toMatchObject({
|
||||
expect(expectLoadedManifest(rootDir, "cursor")).toMatchObject({
|
||||
id: "cursor-sample",
|
||||
name: "Cursor Sample",
|
||||
description: "Cursor fixture",
|
||||
|
|
@ -177,82 +171,69 @@ describe("bundle manifest parsing", () => {
|
|||
fs.writeFileSync(path.join(rootDir, "settings.json"), '{"hideThinkingBlock":true}', "utf-8");
|
||||
|
||||
expect(detectBundleManifestFormat(rootDir)).toBe("claude");
|
||||
const result = loadBundleManifest({ rootDir, bundleFormat: "claude" });
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
expect(result.manifest.id).toBe(path.basename(rootDir).toLowerCase());
|
||||
expect(result.manifest.skills).toEqual(["skills", "commands"]);
|
||||
expect(result.manifest.settingsFiles).toEqual(["settings.json"]);
|
||||
expect(result.manifest.capabilities).toEqual(
|
||||
const manifest = expectLoadedManifest(rootDir, "claude");
|
||||
expect(manifest.id).toBe(path.basename(rootDir).toLowerCase());
|
||||
expect(manifest.skills).toEqual(["skills", "commands"]);
|
||||
expect(manifest.settingsFiles).toEqual(["settings.json"]);
|
||||
expect(manifest.capabilities).toEqual(
|
||||
expect.arrayContaining(["skills", "commands", "settings"]),
|
||||
);
|
||||
});
|
||||
|
||||
it("resolves Claude bundle hooks from default and declared paths", () => {
|
||||
it.each([
|
||||
{
|
||||
name: "resolves Claude bundle hooks from default and declared paths",
|
||||
setupKind: "default-hooks",
|
||||
expectedHooks: ["hooks/hooks.json"],
|
||||
hasHooksCapability: true,
|
||||
},
|
||||
{
|
||||
name: "resolves Claude bundle hooks from manifest-declared paths only",
|
||||
setupKind: "custom-hooks",
|
||||
expectedHooks: ["custom-hooks"],
|
||||
hasHooksCapability: true,
|
||||
},
|
||||
{
|
||||
name: "returns empty hooks for Claude bundles with no hooks directory",
|
||||
setupKind: "no-hooks",
|
||||
expectedHooks: [],
|
||||
hasHooksCapability: false,
|
||||
},
|
||||
] as const)("$name", ({ setupKind, expectedHooks, hasHooksCapability }) => {
|
||||
const rootDir = makeTempDir();
|
||||
mkdirSafe(path.join(rootDir, ".claude-plugin"));
|
||||
mkdirSafe(path.join(rootDir, "hooks"));
|
||||
fs.writeFileSync(path.join(rootDir, "hooks", "hooks.json"), '{"hooks":[]}', "utf-8");
|
||||
fs.writeFileSync(
|
||||
path.join(rootDir, CLAUDE_BUNDLE_MANIFEST_RELATIVE_PATH),
|
||||
JSON.stringify({
|
||||
name: "Hook Plugin",
|
||||
description: "Claude hooks fixture",
|
||||
}),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const result = loadBundleManifest({ rootDir, bundleFormat: "claude" });
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) {
|
||||
return;
|
||||
if (setupKind === "default-hooks") {
|
||||
mkdirSafe(path.join(rootDir, "hooks"));
|
||||
fs.writeFileSync(path.join(rootDir, "hooks", "hooks.json"), '{"hooks":[]}', "utf-8");
|
||||
fs.writeFileSync(
|
||||
path.join(rootDir, CLAUDE_BUNDLE_MANIFEST_RELATIVE_PATH),
|
||||
JSON.stringify({
|
||||
name: "Hook Plugin",
|
||||
description: "Claude hooks fixture",
|
||||
}),
|
||||
"utf-8",
|
||||
);
|
||||
} else if (setupKind === "custom-hooks") {
|
||||
mkdirSafe(path.join(rootDir, "custom-hooks"));
|
||||
fs.writeFileSync(
|
||||
path.join(rootDir, CLAUDE_BUNDLE_MANIFEST_RELATIVE_PATH),
|
||||
JSON.stringify({
|
||||
name: "Custom Hook Plugin",
|
||||
hooks: "custom-hooks",
|
||||
}),
|
||||
"utf-8",
|
||||
);
|
||||
} else {
|
||||
mkdirSafe(path.join(rootDir, "skills"));
|
||||
fs.writeFileSync(
|
||||
path.join(rootDir, CLAUDE_BUNDLE_MANIFEST_RELATIVE_PATH),
|
||||
JSON.stringify({ name: "No Hooks" }),
|
||||
"utf-8",
|
||||
);
|
||||
}
|
||||
expect(result.manifest.hooks).toEqual(["hooks/hooks.json"]);
|
||||
expect(result.manifest.capabilities).toContain("hooks");
|
||||
});
|
||||
|
||||
it("resolves Claude bundle hooks from manifest-declared paths only", () => {
|
||||
const rootDir = makeTempDir();
|
||||
mkdirSafe(path.join(rootDir, ".claude-plugin"));
|
||||
mkdirSafe(path.join(rootDir, "custom-hooks"));
|
||||
fs.writeFileSync(
|
||||
path.join(rootDir, CLAUDE_BUNDLE_MANIFEST_RELATIVE_PATH),
|
||||
JSON.stringify({
|
||||
name: "Custom Hook Plugin",
|
||||
hooks: "custom-hooks",
|
||||
}),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const result = loadBundleManifest({ rootDir, bundleFormat: "claude" });
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) {
|
||||
return;
|
||||
}
|
||||
expect(result.manifest.hooks).toEqual(["custom-hooks"]);
|
||||
expect(result.manifest.capabilities).toContain("hooks");
|
||||
});
|
||||
|
||||
it("returns empty hooks for Claude bundles with no hooks directory", () => {
|
||||
const rootDir = makeTempDir();
|
||||
mkdirSafe(path.join(rootDir, ".claude-plugin"));
|
||||
mkdirSafe(path.join(rootDir, "skills"));
|
||||
fs.writeFileSync(
|
||||
path.join(rootDir, CLAUDE_BUNDLE_MANIFEST_RELATIVE_PATH),
|
||||
JSON.stringify({ name: "No Hooks" }),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const result = loadBundleManifest({ rootDir, bundleFormat: "claude" });
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) {
|
||||
return;
|
||||
}
|
||||
expect(result.manifest.hooks).toEqual([]);
|
||||
expect(result.manifest.capabilities).not.toContain("hooks");
|
||||
const manifest = expectLoadedManifest(rootDir, "claude");
|
||||
expect(manifest.hooks).toEqual(expectedHooks);
|
||||
expect(manifest.capabilities.includes("hooks")).toBe(hasHooksCapability);
|
||||
});
|
||||
|
||||
it("does not misclassify native index plugins as manifestless Claude bundles", () => {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,14 @@ const BUNDLED_PLUGIN_METADATA_TEST_TIMEOUT_MS = 300_000;
|
|||
|
||||
installGeneratedPluginTempRootCleanup();
|
||||
|
||||
function expectTestOnlyArtifactsExcluded(artifacts: readonly string[]) {
|
||||
for (const artifact of artifacts) {
|
||||
expect(artifact).not.toMatch(/^test-/);
|
||||
expect(artifact).not.toContain(".test-");
|
||||
expect(artifact).not.toMatch(/\.test\.js$/);
|
||||
}
|
||||
}
|
||||
|
||||
describe("bundled plugin metadata", () => {
|
||||
it(
|
||||
"matches the generated metadata snapshot",
|
||||
|
|
@ -49,13 +57,9 @@ describe("bundled plugin metadata", () => {
|
|||
});
|
||||
|
||||
it("excludes test-only public surface artifacts", () => {
|
||||
for (const entry of BUNDLED_PLUGIN_METADATA) {
|
||||
for (const artifact of entry.publicSurfaceArtifacts ?? []) {
|
||||
expect(artifact).not.toMatch(/^test-/);
|
||||
expect(artifact).not.toContain(".test-");
|
||||
expect(artifact).not.toMatch(/\.test\.js$/);
|
||||
}
|
||||
}
|
||||
BUNDLED_PLUGIN_METADATA.forEach((entry) =>
|
||||
expectTestOnlyArtifactsExcluded(entry.publicSurfaceArtifacts ?? []),
|
||||
);
|
||||
});
|
||||
|
||||
it("prefers built generated paths when present and falls back to source paths", () => {
|
||||
|
|
|
|||
|
|
@ -23,24 +23,34 @@ describe("bundled provider auth env vars", () => {
|
|||
});
|
||||
|
||||
it("reads bundled provider auth env vars from plugin manifests", () => {
|
||||
expect(BUNDLED_PROVIDER_AUTH_ENV_VAR_CANDIDATES.brave).toEqual(["BRAVE_API_KEY"]);
|
||||
expect(BUNDLED_PROVIDER_AUTH_ENV_VAR_CANDIDATES.firecrawl).toEqual(["FIRECRAWL_API_KEY"]);
|
||||
expect(BUNDLED_PROVIDER_AUTH_ENV_VAR_CANDIDATES["github-copilot"]).toEqual([
|
||||
"COPILOT_GITHUB_TOKEN",
|
||||
"GH_TOKEN",
|
||||
"GITHUB_TOKEN",
|
||||
]);
|
||||
expect(BUNDLED_PROVIDER_AUTH_ENV_VAR_CANDIDATES.perplexity).toEqual([
|
||||
"PERPLEXITY_API_KEY",
|
||||
"OPENROUTER_API_KEY",
|
||||
]);
|
||||
expect(BUNDLED_PROVIDER_AUTH_ENV_VAR_CANDIDATES.tavily).toEqual(["TAVILY_API_KEY"]);
|
||||
expect(BUNDLED_PROVIDER_AUTH_ENV_VAR_CANDIDATES["minimax-portal"]).toEqual([
|
||||
"MINIMAX_OAUTH_TOKEN",
|
||||
"MINIMAX_API_KEY",
|
||||
]);
|
||||
expect(BUNDLED_PROVIDER_AUTH_ENV_VAR_CANDIDATES.openai).toEqual(["OPENAI_API_KEY"]);
|
||||
expect(BUNDLED_PROVIDER_AUTH_ENV_VAR_CANDIDATES.fal).toEqual(["FAL_KEY"]);
|
||||
expect(
|
||||
Object.fromEntries(
|
||||
[
|
||||
["brave", ["BRAVE_API_KEY"]],
|
||||
["firecrawl", ["FIRECRAWL_API_KEY"]],
|
||||
["github-copilot", ["COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"]],
|
||||
["perplexity", ["PERPLEXITY_API_KEY", "OPENROUTER_API_KEY"]],
|
||||
["tavily", ["TAVILY_API_KEY"]],
|
||||
["minimax-portal", ["MINIMAX_OAUTH_TOKEN", "MINIMAX_API_KEY"]],
|
||||
["openai", ["OPENAI_API_KEY"]],
|
||||
["fal", ["FAL_KEY"]],
|
||||
].map(([providerId]) => [
|
||||
providerId,
|
||||
BUNDLED_PROVIDER_AUTH_ENV_VAR_CANDIDATES[
|
||||
providerId as keyof typeof BUNDLED_PROVIDER_AUTH_ENV_VAR_CANDIDATES
|
||||
],
|
||||
]),
|
||||
),
|
||||
).toEqual({
|
||||
brave: ["BRAVE_API_KEY"],
|
||||
firecrawl: ["FIRECRAWL_API_KEY"],
|
||||
"github-copilot": ["COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"],
|
||||
perplexity: ["PERPLEXITY_API_KEY", "OPENROUTER_API_KEY"],
|
||||
tavily: ["TAVILY_API_KEY"],
|
||||
"minimax-portal": ["MINIMAX_OAUTH_TOKEN", "MINIMAX_API_KEY"],
|
||||
openai: ["OPENAI_API_KEY"],
|
||||
fal: ["FAL_KEY"],
|
||||
});
|
||||
expect("openai-codex" in BUNDLED_PROVIDER_AUTH_ENV_VAR_CANDIDATES).toBe(false);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -51,8 +51,9 @@ describe("provider auth choice manifest helpers", () => {
|
|||
expect(resolveManifestProviderAuthChoice("openai-api-key")?.providerId).toBe("openai");
|
||||
});
|
||||
|
||||
it("deduplicates flag metadata by option key + flag", () => {
|
||||
loadPluginManifestRegistry.mockReturnValue({
|
||||
it.each([
|
||||
{
|
||||
name: "deduplicates flag metadata by option key + flag",
|
||||
plugins: [
|
||||
{
|
||||
id: "moonshot",
|
||||
|
|
@ -80,21 +81,19 @@ describe("provider auth choice manifest helpers", () => {
|
|||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(resolveManifestProviderOnboardAuthFlags()).toEqual([
|
||||
{
|
||||
optionKey: "moonshotApiKey",
|
||||
authChoice: "moonshot-api-key",
|
||||
cliFlag: "--moonshot-api-key",
|
||||
cliOption: "--moonshot-api-key <key>",
|
||||
description: "Moonshot API key",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("resolves deprecated auth-choice aliases through manifest metadata", () => {
|
||||
loadPluginManifestRegistry.mockReturnValue({
|
||||
run: () =>
|
||||
expect(resolveManifestProviderOnboardAuthFlags()).toEqual([
|
||||
{
|
||||
optionKey: "moonshotApiKey",
|
||||
authChoice: "moonshot-api-key",
|
||||
cliFlag: "--moonshot-api-key",
|
||||
cliOption: "--moonshot-api-key <key>",
|
||||
description: "Moonshot API key",
|
||||
},
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "resolves deprecated auth-choice aliases through manifest metadata",
|
||||
plugins: [
|
||||
{
|
||||
id: "minimax",
|
||||
|
|
@ -108,14 +107,20 @@ describe("provider auth choice manifest helpers", () => {
|
|||
],
|
||||
},
|
||||
],
|
||||
run: () => {
|
||||
expect(resolveManifestDeprecatedProviderAuthChoice("minimax")?.choiceId).toBe(
|
||||
"minimax-global-api",
|
||||
);
|
||||
expect(resolveManifestDeprecatedProviderAuthChoice("minimax-api")?.choiceId).toBe(
|
||||
"minimax-global-api",
|
||||
);
|
||||
expect(resolveManifestDeprecatedProviderAuthChoice("openai")).toBeUndefined();
|
||||
},
|
||||
},
|
||||
])("$name", ({ plugins, run }) => {
|
||||
loadPluginManifestRegistry.mockReturnValue({
|
||||
plugins,
|
||||
});
|
||||
|
||||
expect(resolveManifestDeprecatedProviderAuthChoice("minimax")?.choiceId).toBe(
|
||||
"minimax-global-api",
|
||||
);
|
||||
expect(resolveManifestDeprecatedProviderAuthChoice("minimax-api")?.choiceId).toBe(
|
||||
"minimax-global-api",
|
||||
);
|
||||
expect(resolveManifestDeprecatedProviderAuthChoice("openai")).toBeUndefined();
|
||||
run();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -23,59 +23,74 @@ function createModel(id: string, name: string): ModelDefinitionConfig {
|
|||
};
|
||||
}
|
||||
describe("provider onboarding preset appliers", () => {
|
||||
it("creates provider and primary-model appliers for a default model preset", () => {
|
||||
const appliers = createDefaultModelPresetAppliers({
|
||||
primaryModelRef: "demo/demo-default",
|
||||
resolveParams: () => ({
|
||||
providerId: "demo",
|
||||
api: "openai-completions" as const,
|
||||
baseUrl: "https://demo.test/v1",
|
||||
defaultModel: createModel("demo-default", "Demo Default"),
|
||||
defaultModelId: "demo-default",
|
||||
aliases: [{ modelRef: "demo/demo-default", alias: "Demo" }],
|
||||
}),
|
||||
});
|
||||
it.each([
|
||||
{
|
||||
name: "creates provider and primary-model appliers for a default model preset",
|
||||
kind: "default-model",
|
||||
},
|
||||
{
|
||||
name: "passes variant args through default-models resolvers",
|
||||
kind: "default-models",
|
||||
},
|
||||
{
|
||||
name: "creates model-catalog appliers that preserve existing aliases",
|
||||
kind: "catalog-models",
|
||||
},
|
||||
] as const)("$name", ({ kind }) => {
|
||||
if (kind === "default-model") {
|
||||
const appliers = createDefaultModelPresetAppliers({
|
||||
primaryModelRef: "demo/demo-default",
|
||||
resolveParams: () => ({
|
||||
providerId: "demo",
|
||||
api: "openai-completions" as const,
|
||||
baseUrl: "https://demo.test/v1",
|
||||
defaultModel: createModel("demo-default", "Demo Default"),
|
||||
defaultModelId: "demo-default",
|
||||
aliases: [{ modelRef: "demo/demo-default", alias: "Demo" }],
|
||||
}),
|
||||
});
|
||||
|
||||
const providerOnly = appliers.applyProviderConfig({});
|
||||
expect(providerOnly.agents?.defaults?.models).toMatchObject({
|
||||
"demo/demo-default": {
|
||||
alias: "Demo",
|
||||
},
|
||||
});
|
||||
expect(providerOnly.agents?.defaults?.model).toBeUndefined();
|
||||
const providerOnly = appliers.applyProviderConfig({});
|
||||
expect(providerOnly.agents?.defaults?.models).toMatchObject({
|
||||
"demo/demo-default": {
|
||||
alias: "Demo",
|
||||
},
|
||||
});
|
||||
expect(providerOnly.agents?.defaults?.model).toBeUndefined();
|
||||
|
||||
const withPrimary = appliers.applyConfig({});
|
||||
expect(withPrimary.agents?.defaults?.model).toEqual({
|
||||
primary: "demo/demo-default",
|
||||
});
|
||||
});
|
||||
const withPrimary = appliers.applyConfig({});
|
||||
expect(withPrimary.agents?.defaults?.model).toEqual({
|
||||
primary: "demo/demo-default",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
it("passes variant args through default-models resolvers", () => {
|
||||
const appliers = createDefaultModelsPresetAppliers<[string]>({
|
||||
primaryModelRef: "demo/a",
|
||||
resolveParams: (_cfg, baseUrl) => ({
|
||||
providerId: "demo",
|
||||
api: "openai-completions" as const,
|
||||
baseUrl,
|
||||
defaultModels: [createModel("a", "Model A"), createModel("b", "Model B")],
|
||||
aliases: [{ modelRef: "demo/a", alias: "Demo A" }],
|
||||
}),
|
||||
});
|
||||
if (kind === "default-models") {
|
||||
const appliers = createDefaultModelsPresetAppliers<[string]>({
|
||||
primaryModelRef: "demo/a",
|
||||
resolveParams: (_cfg, baseUrl) => ({
|
||||
providerId: "demo",
|
||||
api: "openai-completions" as const,
|
||||
baseUrl,
|
||||
defaultModels: [createModel("a", "Model A"), createModel("b", "Model B")],
|
||||
aliases: [{ modelRef: "demo/a", alias: "Demo A" }],
|
||||
}),
|
||||
});
|
||||
|
||||
const cfg = appliers.applyConfig({}, "https://alt.test/v1");
|
||||
expect(cfg.models?.providers?.demo).toMatchObject({
|
||||
baseUrl: "https://alt.test/v1",
|
||||
models: [
|
||||
{ id: "a", name: "Model A" },
|
||||
{ id: "b", name: "Model B" },
|
||||
],
|
||||
});
|
||||
expect(cfg.agents?.defaults?.model).toEqual({
|
||||
primary: "demo/a",
|
||||
});
|
||||
});
|
||||
const cfg = appliers.applyConfig({}, "https://alt.test/v1");
|
||||
expect(cfg.models?.providers?.demo).toMatchObject({
|
||||
baseUrl: "https://alt.test/v1",
|
||||
models: [
|
||||
{ id: "a", name: "Model A" },
|
||||
{ id: "b", name: "Model B" },
|
||||
],
|
||||
});
|
||||
expect(cfg.agents?.defaults?.model).toEqual({
|
||||
primary: "demo/a",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
it("creates model-catalog appliers that preserve existing aliases", () => {
|
||||
const appliers = createModelCatalogPresetAppliers({
|
||||
primaryModelRef: "catalog/default",
|
||||
resolveParams: () => ({
|
||||
|
|
|
|||
|
|
@ -60,92 +60,98 @@ function resolveWizardOptionsTwice(params: {
|
|||
});
|
||||
}
|
||||
|
||||
function expectSingleWizardChoice(params: {
|
||||
provider: ProviderPlugin;
|
||||
choice: string;
|
||||
expectedOption: Record<string, unknown>;
|
||||
expectedWizard: unknown;
|
||||
}) {
|
||||
resolvePluginProviders.mockReturnValue([params.provider]);
|
||||
expect(resolveProviderWizardOptions({})).toEqual([params.expectedOption]);
|
||||
expect(
|
||||
resolveProviderPluginChoice({
|
||||
providers: [params.provider],
|
||||
choice: params.choice,
|
||||
}),
|
||||
).toEqual({
|
||||
provider: params.provider,
|
||||
method: params.provider.auth[0],
|
||||
wizard: params.expectedWizard,
|
||||
});
|
||||
}
|
||||
|
||||
describe("provider wizard boundaries", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("uses explicit setup choice ids and bound method ids", () => {
|
||||
const provider = makeProvider({
|
||||
id: "vllm",
|
||||
label: "vLLM",
|
||||
auth: [
|
||||
{ id: "local", label: "Local", kind: "custom", run: vi.fn() },
|
||||
{ id: "cloud", label: "Cloud", kind: "custom", run: vi.fn() },
|
||||
],
|
||||
wizard: {
|
||||
setup: {
|
||||
choiceId: "self-hosted-vllm",
|
||||
methodId: "local",
|
||||
choiceLabel: "vLLM local",
|
||||
groupId: "local-runtimes",
|
||||
groupLabel: "Local runtimes",
|
||||
it.each([
|
||||
{
|
||||
name: "uses explicit setup choice ids and bound method ids",
|
||||
provider: makeProvider({
|
||||
id: "vllm",
|
||||
label: "vLLM",
|
||||
auth: [
|
||||
{ id: "local", label: "Local", kind: "custom", run: vi.fn() },
|
||||
{ id: "cloud", label: "Cloud", kind: "custom", run: vi.fn() },
|
||||
],
|
||||
wizard: {
|
||||
setup: {
|
||||
choiceId: "self-hosted-vllm",
|
||||
methodId: "local",
|
||||
choiceLabel: "vLLM local",
|
||||
groupId: "local-runtimes",
|
||||
groupLabel: "Local runtimes",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
resolvePluginProviders.mockReturnValue([provider]);
|
||||
|
||||
expect(resolveProviderWizardOptions({})).toEqual([
|
||||
{
|
||||
}),
|
||||
choice: "self-hosted-vllm",
|
||||
expectedOption: {
|
||||
value: "self-hosted-vllm",
|
||||
label: "vLLM local",
|
||||
groupId: "local-runtimes",
|
||||
groupLabel: "Local runtimes",
|
||||
},
|
||||
]);
|
||||
expect(
|
||||
resolveProviderPluginChoice({
|
||||
providers: [provider],
|
||||
choice: "self-hosted-vllm",
|
||||
}),
|
||||
).toEqual({
|
||||
provider,
|
||||
method: provider.auth[0],
|
||||
wizard: provider.wizard?.setup,
|
||||
});
|
||||
});
|
||||
|
||||
it("builds wizard options from method-level metadata", () => {
|
||||
const provider = makeProvider({
|
||||
id: "openai",
|
||||
label: "OpenAI",
|
||||
auth: [
|
||||
{
|
||||
id: "api-key",
|
||||
label: "OpenAI API key",
|
||||
kind: "api_key",
|
||||
wizard: {
|
||||
choiceId: "openai-api-key",
|
||||
choiceLabel: "OpenAI API key",
|
||||
groupId: "openai",
|
||||
groupLabel: "OpenAI",
|
||||
onboardingScopes: ["text-inference"],
|
||||
resolveWizard: (provider: ProviderPlugin) => provider.wizard?.setup,
|
||||
},
|
||||
{
|
||||
name: "builds wizard options from method-level metadata",
|
||||
provider: makeProvider({
|
||||
id: "openai",
|
||||
label: "OpenAI",
|
||||
auth: [
|
||||
{
|
||||
id: "api-key",
|
||||
label: "OpenAI API key",
|
||||
kind: "api_key",
|
||||
wizard: {
|
||||
choiceId: "openai-api-key",
|
||||
choiceLabel: "OpenAI API key",
|
||||
groupId: "openai",
|
||||
groupLabel: "OpenAI",
|
||||
onboardingScopes: ["text-inference"],
|
||||
},
|
||||
run: vi.fn(),
|
||||
},
|
||||
run: vi.fn(),
|
||||
},
|
||||
],
|
||||
});
|
||||
resolvePluginProviders.mockReturnValue([provider]);
|
||||
|
||||
expect(resolveProviderWizardOptions({})).toEqual([
|
||||
{
|
||||
],
|
||||
}),
|
||||
choice: "openai-api-key",
|
||||
expectedOption: {
|
||||
value: "openai-api-key",
|
||||
label: "OpenAI API key",
|
||||
groupId: "openai",
|
||||
groupLabel: "OpenAI",
|
||||
onboardingScopes: ["text-inference"],
|
||||
},
|
||||
]);
|
||||
expect(
|
||||
resolveProviderPluginChoice({
|
||||
providers: [provider],
|
||||
choice: "openai-api-key",
|
||||
}),
|
||||
).toEqual({
|
||||
resolveWizard: (provider: ProviderPlugin) => provider.auth[0]?.wizard,
|
||||
},
|
||||
] as const)("$name", ({ provider, choice, expectedOption, resolveWizard }) => {
|
||||
expectSingleWizardChoice({
|
||||
provider,
|
||||
method: provider.auth[0],
|
||||
wizard: provider.auth[0]?.wizard,
|
||||
choice,
|
||||
expectedOption,
|
||||
expectedWizard: resolveWizard(provider),
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -317,27 +323,24 @@ describe("provider wizard boundaries", () => {
|
|||
expect(resolvePluginProviders).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("skips provider-wizard memoization when plugin cache opt-outs are set", () => {
|
||||
it.each([
|
||||
{
|
||||
name: "skips provider-wizard memoization when plugin cache opt-outs are set",
|
||||
env: {
|
||||
OPENCLAW_HOME: "/tmp/openclaw-home",
|
||||
OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE: "1",
|
||||
} as NodeJS.ProcessEnv,
|
||||
},
|
||||
{
|
||||
name: "skips provider-wizard memoization when discovery cache ttl is zero",
|
||||
env: {
|
||||
OPENCLAW_HOME: "/tmp/openclaw-home",
|
||||
OPENCLAW_PLUGIN_DISCOVERY_CACHE_MS: "0",
|
||||
} as NodeJS.ProcessEnv,
|
||||
},
|
||||
] as const)("$name", ({ env }) => {
|
||||
const provider = createSglangSetupProvider();
|
||||
const config = createSglangConfig();
|
||||
const env = {
|
||||
OPENCLAW_HOME: "/tmp/openclaw-home",
|
||||
OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE: "1",
|
||||
} as NodeJS.ProcessEnv;
|
||||
resolvePluginProviders.mockReturnValue([provider]);
|
||||
|
||||
resolveWizardOptionsTwice({ config, env });
|
||||
|
||||
expect(resolvePluginProviders).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("skips provider-wizard memoization when discovery cache ttl is zero", () => {
|
||||
const provider = createSglangSetupProvider();
|
||||
const config = createSglangConfig();
|
||||
const env = {
|
||||
OPENCLAW_HOME: "/tmp/openclaw-home",
|
||||
OPENCLAW_PLUGIN_DISCOVERY_CACHE_MS: "0",
|
||||
} as NodeJS.ProcessEnv;
|
||||
resolvePluginProviders.mockReturnValue([provider]);
|
||||
|
||||
resolveWizardOptionsTwice({ config, env });
|
||||
|
|
|
|||
Loading…
Reference in New Issue