|
1 | 1 | // Tool allowlist tests cover tool availability for isolated cron runs. |
2 | 2 | import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; |
| 3 | +import { MISSING_WEB_SEARCH_PROVIDER_DIAGNOSTIC_MESSAGE } from "../run-diagnostics.js"; |
3 | 4 | import "../../agents/test-helpers/fast-coding-tools.js"; |
4 | 5 | import { |
| 6 | + listWebSearchProvidersMock, |
| 7 | + loadModelCatalogMock, |
5 | 8 | loadRunCronIsolatedAgentTurn, |
| 9 | + resolveConfiguredModelRefMock, |
6 | 10 | resetRunCronIsolatedAgentTurnHarness, |
7 | 11 | resolveDeliveryTargetMock, |
| 12 | + resolveWebSearchProviderIdMock, |
8 | 13 | runEmbeddedAgentMock, |
9 | 14 | runWithModelFallbackMock, |
10 | 15 | } from "./run.test-harness.js"; |
@@ -46,6 +51,23 @@ function makeParamsWithToolsAllow(toolsAllow: string[]) { |
46 | 51 | }; |
47 | 52 | } |
48 | 53 |
|
| 54 | +function makeParamsWithDefaultToolsAllow(toolsAllow: string[]) { |
| 55 | + const params = makeParams(); |
| 56 | + const job = params.job as Record<string, unknown>; |
| 57 | + return { |
| 58 | + ...params, |
| 59 | + job: { |
| 60 | + ...job, |
| 61 | + payload: { |
| 62 | + kind: "agentTurn", |
| 63 | + message: "check allowed tools", |
| 64 | + toolsAllow, |
| 65 | + toolsAllowIsDefault: true, |
| 66 | + }, |
| 67 | + } as never, |
| 68 | + }; |
| 69 | +} |
| 70 | + |
49 | 71 | function requireEmbeddedAgentCall(): { |
50 | 72 | jobId?: string; |
51 | 73 | toolsAllow?: string[]; |
@@ -127,4 +149,114 @@ describe("runCronIsolatedAgentTurn toolsAllow passthrough", () => { |
127 | 149 | expect(call.toolsAllow).toEqual(["maniple__check_idle_workers"]); |
128 | 150 | }, |
129 | 151 | ); |
| 152 | + |
| 153 | + it( |
| 154 | + "adds cron diagnostics when web_search is allowed without a selected provider", |
| 155 | + { timeout: RUN_TOOLS_ALLOW_TIMEOUT_MS }, |
| 156 | + async () => { |
| 157 | + listWebSearchProvidersMock.mockReturnValue([{ id: "duckduckgo" }]); |
| 158 | + resolveWebSearchProviderIdMock.mockReturnValue(""); |
| 159 | + |
| 160 | + const result = await runCronIsolatedAgentTurn(makeParamsWithToolsAllow(["web_search"])); |
| 161 | + |
| 162 | + expect(result.status).toBe("ok"); |
| 163 | + expect(runEmbeddedAgentMock).toHaveBeenCalledTimes(1); |
| 164 | + const call = requireEmbeddedAgentCall(); |
| 165 | + expect(call.toolsAllow).toEqual(["web_search"]); |
| 166 | + expect(result.diagnostics?.summary).toBe(MISSING_WEB_SEARCH_PROVIDER_DIAGNOSTIC_MESSAGE); |
| 167 | + expect(result.diagnostics?.entries).toEqual([ |
| 168 | + { |
| 169 | + ts: expect.any(Number), |
| 170 | + source: "cron-preflight", |
| 171 | + severity: "warn", |
| 172 | + message: MISSING_WEB_SEARCH_PROVIDER_DIAGNOSTIC_MESSAGE, |
| 173 | + toolName: "web_search", |
| 174 | + }, |
| 175 | + ]); |
| 176 | + }, |
| 177 | + ); |
| 178 | + |
| 179 | + it( |
| 180 | + "does not warn for default-derived toolsAllow that includes web_search", |
| 181 | + { timeout: RUN_TOOLS_ALLOW_TIMEOUT_MS }, |
| 182 | + async () => { |
| 183 | + listWebSearchProvidersMock.mockReturnValue([]); |
| 184 | + |
| 185 | + const result = await runCronIsolatedAgentTurn( |
| 186 | + makeParamsWithDefaultToolsAllow(["web_search"]), |
| 187 | + ); |
| 188 | + |
| 189 | + expect(result.status).toBe("ok"); |
| 190 | + expect(result.diagnostics).toBeUndefined(); |
| 191 | + }, |
| 192 | + ); |
| 193 | + |
| 194 | + it( |
| 195 | + "does not warn when native web_search suppresses the managed provider tool", |
| 196 | + { timeout: RUN_TOOLS_ALLOW_TIMEOUT_MS }, |
| 197 | + async () => { |
| 198 | + listWebSearchProvidersMock.mockReturnValue([]); |
| 199 | + resolveConfiguredModelRefMock.mockReturnValue({ |
| 200 | + provider: "gateway", |
| 201 | + model: "gpt-5.5", |
| 202 | + }); |
| 203 | + loadModelCatalogMock.mockResolvedValue([ |
| 204 | + { |
| 205 | + id: "gpt-5.5", |
| 206 | + name: "GPT-5.5", |
| 207 | + provider: "gateway", |
| 208 | + api: "openai-chatgpt-responses", |
| 209 | + }, |
| 210 | + ]); |
| 211 | + |
| 212 | + const result = await runCronIsolatedAgentTurn({ |
| 213 | + ...makeParamsWithToolsAllow(["web_search"]), |
| 214 | + cfg: { |
| 215 | + tools: { |
| 216 | + web: { |
| 217 | + search: { |
| 218 | + enabled: true, |
| 219 | + openaiCodex: { |
| 220 | + enabled: true, |
| 221 | + mode: "cached", |
| 222 | + }, |
| 223 | + }, |
| 224 | + }, |
| 225 | + }, |
| 226 | + }, |
| 227 | + }); |
| 228 | + |
| 229 | + expect(result.status).toBe("ok"); |
| 230 | + expect(result.diagnostics).toBeUndefined(); |
| 231 | + }, |
| 232 | + ); |
| 233 | + |
| 234 | + it( |
| 235 | + "keeps web_search provider diagnostics when the run aborts", |
| 236 | + { timeout: RUN_TOOLS_ALLOW_TIMEOUT_MS }, |
| 237 | + async () => { |
| 238 | + listWebSearchProvidersMock.mockReturnValue([]); |
| 239 | + resolveWebSearchProviderIdMock.mockReturnValue(""); |
| 240 | + runWithModelFallbackMock.mockResolvedValueOnce({ |
| 241 | + result: { |
| 242 | + payloads: [], |
| 243 | + meta: { |
| 244 | + aborted: true, |
| 245 | + agentMeta: {}, |
| 246 | + }, |
| 247 | + }, |
| 248 | + provider: "openai", |
| 249 | + model: "gpt-5.4", |
| 250 | + attempts: [], |
| 251 | + }); |
| 252 | + |
| 253 | + const result = await runCronIsolatedAgentTurn(makeParamsWithToolsAllow(["web_search"])); |
| 254 | + |
| 255 | + expect(result.status).toBe("error"); |
| 256 | + expect(result.diagnostics?.entries.map((entry) => entry.message)).toEqual([ |
| 257 | + MISSING_WEB_SEARCH_PROVIDER_DIAGNOSTIC_MESSAGE, |
| 258 | + "cron isolated agent run aborted", |
| 259 | + ]); |
| 260 | + }, |
| 261 | + ); |
130 | 262 | }); |
0 commit comments