diff --git a/agent/tools/web_test.go b/agent/tools/web_test.go index 17c3cd238..a9327820f 100644 --- a/agent/tools/web_test.go +++ b/agent/tools/web_test.go @@ -5,11 +5,15 @@ import ( "errors" "net/http" "net/http/httptest" + "os" + "path/filepath" "strings" "testing" coreagent "github.com/ollama/ollama/agent" "github.com/ollama/ollama/api" + "github.com/ollama/ollama/envconfig" + internalcloud "github.com/ollama/ollama/internal/cloud" ) func TestWebToolsRequireApproval(t *testing.T) { @@ -22,13 +26,31 @@ func TestWebToolsRequireApproval(t *testing.T) { } var webToolCases = []struct { - name string - tool coreagent.Tool - args map[string]any - path string + name string + tool coreagent.Tool + args map[string]any + path string + operation string }{ - {"search", &WebSearch{}, map[string]any{"query": "ollama"}, "/api/experimental/web_search"}, - {"fetch", &WebFetch{}, map[string]any{"url": "https://ollama.com"}, "/api/experimental/web_fetch"}, + {"search", &WebSearch{}, map[string]any{"query": "ollama"}, "/api/experimental/web_search", "web search is unavailable"}, + {"fetch", &WebFetch{}, map[string]any{"url": "https://ollama.com"}, "/api/experimental/web_fetch", "web fetch is unavailable"}, +} + +// enableWebToolsForTest isolates web tool tests from the runner's cloud +// policy. In particular, Windows can inherit both OLLAMA_NO_CLOUD and a +// server.json from USERPROFILE. +func enableWebToolsForTest(t *testing.T) { + t.Helper() + + // Register before t.Setenv so the cache is refreshed after t.Setenv has + // restored the runner's environment during cleanup. + t.Cleanup(envconfig.ReloadServerConfig) + + home := t.TempDir() + t.Setenv("HOME", home) + t.Setenv("USERPROFILE", home) + t.Setenv("OLLAMA_NO_CLOUD", "") + envconfig.ReloadServerConfig() } // runWebTool executes tool against a stub server that responds to every @@ -50,6 +72,8 @@ func runWebTool(t *testing.T, tool coreagent.Tool, args map[string]any, path str } func TestWebToolsReportAuthenticationError(t *testing.T) { + enableWebToolsForTest(t) + for _, tt := range webToolCases { t.Run(tt.name, func(t *testing.T) { err := runWebTool(t, tt.tool, tt.args, tt.path, http.StatusUnauthorized, @@ -62,6 +86,8 @@ func TestWebToolsReportAuthenticationError(t *testing.T) { } func TestWebToolsPreserveNonAuthenticationErrors(t *testing.T) { + enableWebToolsForTest(t) + for _, tt := range webToolCases { t.Run(tt.name, func(t *testing.T) { err := runWebTool(t, tt.tool, tt.args, tt.path, http.StatusTooManyRequests, @@ -76,7 +102,35 @@ func TestWebToolsPreserveNonAuthenticationErrors(t *testing.T) { } } +func TestWebToolsIgnoreInheritedCloudPolicy(t *testing.T) { + // This cleanup is registered before the test environment, so it restores + // the server config cache after t.Setenv restores the runner's values. + t.Cleanup(envconfig.ReloadServerConfig) + + home := t.TempDir() + configPath := filepath.Join(home, ".ollama", "server.json") + if err := os.MkdirAll(filepath.Dir(configPath), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(configPath, []byte(`{"disable_ollama_cloud":true}`), 0o644); err != nil { + t.Fatal(err) + } + t.Setenv("HOME", home) + t.Setenv("USERPROFILE", home) + t.Setenv("OLLAMA_NO_CLOUD", "1") + envconfig.ReloadServerConfig() + + enableWebToolsForTest(t) + err := runWebTool(t, &WebSearch{}, map[string]any{"query": "ollama"}, "/api/experimental/web_search", http.StatusUnauthorized, + `{"error":"unauthorized","signin_url":"https://ollama.com/signin"}`) + if !errors.Is(err, ErrWebAuthRequired) { + t.Fatalf("error = %v, want %v", err, ErrWebAuthRequired) + } +} + func TestWebFetchRejectsUnsupportedScheme(t *testing.T) { + enableWebToolsForTest(t) + tests := []struct { name string url string @@ -106,6 +160,8 @@ func TestWebFetchRejectsUnsupportedScheme(t *testing.T) { } func TestWebFetchBoundsContentBeforeReturning(t *testing.T) { + enableWebToolsForTest(t) + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api/experimental/web_fetch" { t.Fatalf("path = %q, want /api/experimental/web_fetch", r.URL.Path) @@ -142,3 +198,17 @@ func TestWebFetchBoundsContentBeforeReturning(t *testing.T) { t.Fatalf("captured content count = %d, want %d", count, maxWebFetchContentRunes) } } + +func TestWebToolsRejectWhenCloudDisabled(t *testing.T) { + t.Setenv("OLLAMA_NO_CLOUD", "1") + + for _, tt := range webToolCases { + t.Run(tt.name, func(t *testing.T) { + _, err := tt.tool.Execute(t.Context(), coreagent.ToolContext{}, tt.args) + want := internalcloud.DisabledError(tt.operation) + if err == nil || err.Error() != want { + t.Fatalf("error = %v, want %q", err, want) + } + }) + } +}