





















@@ -312,6 +312,41 @@ function readProviderKey(config: OpenClawConfig, provider: ProviderUnderTest): u
312312return pluginConfig?.webSearch?.apiKey;
313313}
314314315+function requireRecord(value: unknown, label: string): Record<string, unknown> {
316+expect(value, label).toBeTypeOf("object");
317+expect(value, label).not.toBeNull();
318+return value as Record<string, unknown>;
319+}
320+321+function diagnostics(value: unknown) {
322+expect(Array.isArray(value), "diagnostics").toBe(true);
323+return value as Array<Record<string, unknown>>;
324+}
325+326+function expectDiagnostic(
327+value: unknown,
328+fields: { code: string; path?: string; messageIncludes?: string },
329+) {
330+const diagnostic = diagnostics(value).find(
331+(candidate) =>
332+candidate.code === fields.code &&
333+(fields.path === undefined || candidate.path === fields.path),
334+);
335+expect(diagnostic, `${fields.code}${fields.path ? ` ${fields.path}` : ""}`).toBeDefined();
336+if (fields.messageIncludes) {
337+expect(typeof diagnostic?.message).toBe("string");
338+expect(diagnostic?.message).toContain(fields.messageIncludes);
339+}
340+}
341+342+function expectNoDiagnosticCode(value: unknown, code: string) {
343+expect(diagnostics(value).some((diagnostic) => diagnostic.code === code)).toBe(false);
344+}
345+346+function firstMockArg(source: { mock: { calls: Array<Array<unknown>> } }) {
347+return requireRecord(source.mock.calls[0]?.[0], "mock call options");
348+}
349+315350describe("runtime web tools resolution", () => {
316351beforeAll(async () => {
317352secretResolve = await import("./resolve.js");
@@ -524,14 +559,10 @@ describe("runtime web tools resolution", () => {
524559525560expect(metadata.search.selectedProvider).toBe("duckduckgo");
526561expect(metadata.search.providerSource).toBe("auto-detect");
527-expect(metadata.search.diagnostics).toEqual(
528-expect.arrayContaining([
529-expect.objectContaining({
530-code: "WEB_SEARCH_AUTODETECT_SELECTED",
531-message: expect.stringContaining('keyless provider "duckduckgo"'),
532-}),
533-]),
534-);
562+expectDiagnostic(metadata.search.diagnostics, {
563+code: "WEB_SEARCH_AUTODETECT_SELECTED",
564+messageIncludes: 'keyless provider "duckduckgo"',
565+});
535566});
536567537568it.each([
@@ -683,14 +714,22 @@ describe("runtime web tools resolution", () => {
683714expect(metadata.search.providerSource).toBe("auto-detect");
684715expect(metadata.search.selectedProvider).toBe("brave");
685716expect(readProviderKey(resolvedConfig, "brave")).toBe("brave-precedence-key");
686-expect(context.warnings).toEqual(
687-expect.arrayContaining([
688-expect.objectContaining({ path: "plugins.entries.google.config.webSearch.apiKey" }),
689-expect.objectContaining({ path: "plugins.entries.xai.config.webSearch.apiKey" }),
690-expect.objectContaining({ path: "plugins.entries.moonshot.config.webSearch.apiKey" }),
691-expect.objectContaining({ path: "plugins.entries.perplexity.config.webSearch.apiKey" }),
692-]),
693-);
717+expectDiagnostic(context.warnings, {
718+code: "SECRETS_REF_IGNORED_INACTIVE_SURFACE",
719+path: "plugins.entries.google.config.webSearch.apiKey",
720+});
721+expectDiagnostic(context.warnings, {
722+code: "SECRETS_REF_IGNORED_INACTIVE_SURFACE",
723+path: "plugins.entries.xai.config.webSearch.apiKey",
724+});
725+expectDiagnostic(context.warnings, {
726+code: "SECRETS_REF_IGNORED_INACTIVE_SURFACE",
727+path: "plugins.entries.moonshot.config.webSearch.apiKey",
728+});
729+expectDiagnostic(context.warnings, {
730+code: "SECRETS_REF_IGNORED_INACTIVE_SURFACE",
731+path: "plugins.entries.perplexity.config.webSearch.apiKey",
732+});
694733});
695734696735it("auto-detects first available provider and keeps lower-priority refs inactive", async () => {
@@ -738,14 +777,10 @@ describe("runtime web tools resolution", () => {
738777provider: "default",
739778id: "MISSING_GEMINI_API_KEY_REF",
740779});
741-expect(context.warnings).toEqual(
742-expect.arrayContaining([
743-expect.objectContaining({
744-code: "SECRETS_REF_IGNORED_INACTIVE_SURFACE",
745-path: "plugins.entries.google.config.webSearch.apiKey",
746-}),
747-]),
748-);
780+expectDiagnostic(context.warnings, {
781+code: "SECRETS_REF_IGNORED_INACTIVE_SURFACE",
782+path: "plugins.entries.google.config.webSearch.apiKey",
783+});
749784expect(context.warnings.map((warning) => warning.code)).not.toContain(
750785"WEB_SEARCH_KEY_UNRESOLVED_NO_FALLBACK",
751786);
@@ -790,14 +825,10 @@ describe("runtime web tools resolution", () => {
790825expect(metadata.search.providerSource).toBe("auto-detect");
791826expect(metadata.search.selectedProvider).toBe("gemini");
792827expect(readProviderKey(resolvedConfig, "gemini")).toBe("gemini-runtime-key");
793-expect(context.warnings).toEqual(
794-expect.arrayContaining([
795-expect.objectContaining({
796-code: "SECRETS_REF_IGNORED_INACTIVE_SURFACE",
797-path: "plugins.entries.brave.config.webSearch.apiKey",
798-}),
799-]),
800-);
828+expectDiagnostic(context.warnings, {
829+code: "SECRETS_REF_IGNORED_INACTIVE_SURFACE",
830+path: "plugins.entries.brave.config.webSearch.apiKey",
831+});
801832expect(context.warnings.map((warning) => warning.code)).not.toContain(
802833"WEB_SEARCH_KEY_UNRESOLVED_NO_FALLBACK",
803834);
@@ -890,22 +921,14 @@ describe("runtime web tools resolution", () => {
890921expect(metadata.search.providerSource).toBe("auto-detect");
891922expect(metadata.search.selectedProvider).toBe("gemini");
892923expect(readProviderKey(resolvedConfig, "gemini")).toBe("gemini-runtime-key");
893-expect(metadata.search.diagnostics).toEqual(
894-expect.arrayContaining([
895-expect.objectContaining({
896-code: "WEB_SEARCH_PROVIDER_INVALID_AUTODETECT",
897-path: "tools.web.search.provider",
898-}),
899-]),
900-);
901-expect(context.warnings).toEqual(
902-expect.arrayContaining([
903-expect.objectContaining({
904-code: "WEB_SEARCH_PROVIDER_INVALID_AUTODETECT",
905-path: "tools.web.search.provider",
906-}),
907-]),
908-);
924+expectDiagnostic(metadata.search.diagnostics, {
925+code: "WEB_SEARCH_PROVIDER_INVALID_AUTODETECT",
926+path: "tools.web.search.provider",
927+});
928+expectDiagnostic(context.warnings, {
929+code: "WEB_SEARCH_PROVIDER_INVALID_AUTODETECT",
930+path: "tools.web.search.provider",
931+});
909932});
910933911934it("fails fast when configured provider ref is unresolved with no fallback", async () => {
@@ -943,14 +966,10 @@ describe("runtime web tools resolution", () => {
943966 context,
944967}),
945968).rejects.toThrow("[WEB_SEARCH_KEY_UNRESOLVED_NO_FALLBACK]");
946-expect(context.warnings).toEqual(
947-expect.arrayContaining([
948-expect.objectContaining({
949-code: "WEB_SEARCH_KEY_UNRESOLVED_NO_FALLBACK",
950-path: "plugins.entries.google.config.webSearch.apiKey",
951-}),
952-]),
953-);
969+expectDiagnostic(context.warnings, {
970+code: "WEB_SEARCH_KEY_UNRESOLVED_NO_FALLBACK",
971+path: "plugins.entries.google.config.webSearch.apiKey",
972+});
954973});
955974956975it("uses bundled-only runtime provider resolution for configured bundled providers", async () => {
@@ -1154,14 +1173,10 @@ describe("runtime web tools resolution", () => {
1154117311551174expect(metadata.search.providerConfigured).toBe("gemini");
11561175expect(metadata.search.providerSource).toBe("configured");
1157-expect(context.warnings).toEqual(
1158-expect.arrayContaining([
1159-expect.objectContaining({
1160-code: "SECRETS_REF_IGNORED_INACTIVE_SURFACE",
1161-path: "plugins.entries.google.config.webSearch.apiKey",
1162-}),
1163-]),
1164-);
1176+expectDiagnostic(context.warnings, {
1177+code: "SECRETS_REF_IGNORED_INACTIVE_SURFACE",
1178+path: "plugins.entries.google.config.webSearch.apiKey",
1179+});
11651180});
1166118111671182it("emits inactive warnings for configured and lower-priority web-search providers when search is disabled", async () => {
@@ -1193,14 +1208,10 @@ describe("runtime web tools resolution", () => {
11931208}),
11941209});
119512101196-expect(context.warnings).toEqual(
1197-expect.arrayContaining([
1198-expect.objectContaining({
1199-code: "SECRETS_REF_IGNORED_INACTIVE_SURFACE",
1200-path: "plugins.entries.google.config.webSearch.apiKey",
1201-}),
1202-]),
1203-);
1211+expectDiagnostic(context.warnings, {
1212+code: "SECRETS_REF_IGNORED_INACTIVE_SURFACE",
1213+path: "plugins.entries.google.config.webSearch.apiKey",
1214+});
12041215});
1205121612061217it("does not auto-enable search when tools.web.search is absent", async () => {
@@ -1268,11 +1279,7 @@ describe("runtime web tools resolution", () => {
1268127912691280expect(metadata.search.selectedProvider).toBe("brave");
12701281expect(resolveBundledWebSearchProvidersFromPublicArtifactsMock).not.toHaveBeenCalled();
1271-expect(resolvePluginWebSearchProvidersMock).toHaveBeenCalledWith(
1272-expect.objectContaining({
1273-bundledAllowlistCompat: true,
1274-}),
1275-);
1282+expect(firstMockArg(resolvePluginWebSearchProvidersMock).bundledAllowlistCompat).toBe(true);
12761283});
1277128412781285it("uses bundled public artifacts for bundled web fetch provider discovery", async () => {
@@ -1321,11 +1328,7 @@ describe("runtime web tools resolution", () => {
1321132813221329expect(metadata.fetch.selectedProvider).toBe("firecrawl");
13231330expect(resolveBundledWebFetchProvidersFromPublicArtifactsMock).not.toHaveBeenCalled();
1324-expect(resolvePluginWebFetchProvidersMock).toHaveBeenCalledWith(
1325-expect.objectContaining({
1326-bundledAllowlistCompat: true,
1327-}),
1328-);
1331+expect(firstMockArg(resolvePluginWebFetchProvidersMock).bundledAllowlistCompat).toBe(true);
13291332});
1330133313311334it("uses env fallback for unresolved web fetch provider SecretRef when active", async () => {
@@ -1364,14 +1367,10 @@ describe("runtime web tools resolution", () => {
13641367| undefined
13651368)?.webFetch?.apiKey,
13661369).toBe("firecrawl-fallback-key");
1367-expect(context.warnings).toEqual(
1368-expect.arrayContaining([
1369-expect.objectContaining({
1370-code: "WEB_FETCH_PROVIDER_KEY_UNRESOLVED_FALLBACK_USED",
1371-path: "plugins.entries.firecrawl.config.webFetch.apiKey",
1372-}),
1373-]),
1374-);
1370+expectDiagnostic(context.warnings, {
1371+code: "WEB_FETCH_PROVIDER_KEY_UNRESOLVED_FALLBACK_USED",
1372+path: "plugins.entries.firecrawl.config.webFetch.apiKey",
1373+});
13751374});
1376137513771376it("resolves plugin-owned web fetch SecretRefs without tools.web.fetch", async () => {
@@ -1470,14 +1469,10 @@ describe("runtime web tools resolution", () => {
14701469 context,
14711470}),
14721471).rejects.toThrow("[WEB_FETCH_PROVIDER_KEY_UNRESOLVED_NO_FALLBACK]");
1473-expect(context.warnings).toEqual(
1474-expect.arrayContaining([
1475-expect.objectContaining({
1476-code: "WEB_FETCH_PROVIDER_KEY_UNRESOLVED_NO_FALLBACK",
1477-path: "plugins.entries.firecrawl.config.webFetch.apiKey",
1478-}),
1479-]),
1480-);
1472+expectDiagnostic(context.warnings, {
1473+code: "WEB_FETCH_PROVIDER_KEY_UNRESOLVED_NO_FALLBACK",
1474+path: "plugins.entries.firecrawl.config.webFetch.apiKey",
1475+});
14811476});
1482147714831478it("rejects env SecretRefs for web fetch provider keys outside provider allowlists", async () => {
@@ -1516,17 +1511,11 @@ describe("runtime web tools resolution", () => {
15161511 context,
15171512}),
15181513).rejects.toThrow("[WEB_FETCH_PROVIDER_KEY_UNRESOLVED_NO_FALLBACK]");
1519-expect(context.warnings).toEqual(
1520-expect.arrayContaining([
1521-expect.objectContaining({
1522-code: "WEB_FETCH_PROVIDER_KEY_UNRESOLVED_NO_FALLBACK",
1523-path: "plugins.entries.firecrawl.config.webFetch.apiKey",
1524-message: expect.stringContaining(
1525-'SecretRef env var "AWS_SECRET_ACCESS_KEY" is not allowed.',
1526-),
1527-}),
1528-]),
1529-);
1514+expectDiagnostic(context.warnings, {
1515+code: "WEB_FETCH_PROVIDER_KEY_UNRESOLVED_NO_FALLBACK",
1516+path: "plugins.entries.firecrawl.config.webFetch.apiKey",
1517+messageIncludes: 'SecretRef env var "AWS_SECRET_ACCESS_KEY" is not allowed.',
1518+});
15301519});
1531152015321521it("keeps web fetch provider discovery bundled-only during runtime secret resolution", async () => {
@@ -1642,11 +1631,7 @@ describe("runtime web tools resolution", () => {
16421631expect(metadata.search.selectedProvider).toBe("brave");
16431632expect(metadata.search.providerSource).toBe("configured");
16441633expect(metadata.search.selectedProviderKeySource).toBe("config");
1645-expect(context.warnings).not.toEqual(
1646-expect.arrayContaining([
1647-expect.objectContaining({ code: "WEB_SEARCH_PROVIDER_INVALID_AUTODETECT" }),
1648-]),
1649-);
1634+expectNoDiagnosticCode(context.warnings, "WEB_SEARCH_PROVIDER_INVALID_AUTODETECT");
16501635expect(resolvePluginWebSearchProvidersMock).toHaveBeenCalledTimes(1);
16511636expect(
16521637resolveBundledExplicitWebSearchProvidersFromPublicArtifactsMock,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。