返回 DeepSeek-Reasonix
network_proxy_app_test.go
根目录 / desktop / network_proxy_app_test.go
1 package main
2
3 import (
4 "os"
5 "path/filepath"
6 "slices"
7 "testing"
8
9 "reasonix/internal/config"
10 "reasonix/internal/netclient"
11 )
12
13 // The proxy is the user's: a checkout's [network] is ignored.
14 func TestNetworkProxySpecForRootKeepsTheUsersProxy(t *testing.T) {
15 isolateDesktopUserDirs(t)
16 if err := os.MkdirAll(filepath.Dir(config.UserConfigPath()), 0o700); err != nil {
17 t.Fatal(err)
18 }
19 if err := os.WriteFile(config.UserConfigPath(), []byte("[network]\nproxy_mode = \"off\"\n"), 0o600); err != nil {
20 t.Fatal(err)
21 }
22 root := t.TempDir()
23 if err := os.WriteFile(filepath.Join(root, ".env"), []byte("PROJECT_PROXY_URL=http://127.0.0.1:9876\n"), 0o600); err != nil {
24 t.Fatal(err)
25 }
26 if err := os.WriteFile(filepath.Join(root, "reasonix.toml"), []byte("[network]\nproxy_mode = \"custom\"\nproxy_url = \"${PROJECT_PROXY_URL}\"\n"), 0o600); err != nil {
27 t.Fatal(err)
28 }
29
30 spec := NewApp().networkProxySpecForRoot(root)
31 if spec.Mode == netclient.ModeCustom || spec.URL != "" {
32 t.Fatalf("model probe proxy = %+v, want the user's proxy_mode off", spec)
33 }
34 }
35
36 // TestSaveProviderPersistsNoProxy pins the #9560 escape hatch: the custom
37 // provider editor's "connect directly" toggle must round-trip through the
38 // config entry so the provider host lands in the chat transport's direct list.
39 func TestSaveProviderPersistsNoProxy(t *testing.T) {
40 isolateDesktopUserDirs(t)
41
42 app := NewApp()
43 if err := app.SaveProvider(ProviderView{
44 Name: "custom-gateway",
45 Kind: "openai",
46 BaseURL: "https://gw.example.internal/v1",
47 Models: []string{"gw-model"},
48 APIKeyEnv: "GW_API_KEY",
49 NoProxy: true,
50 }); err != nil {
51 t.Fatalf("SaveProvider: %v", err)
52 }
53
54 cfg := config.LoadForEdit(config.UserConfigPath())
55 got, ok := cfg.Provider("custom-gateway")
56 if !ok {
57 t.Fatal("saved provider not found")
58 }
59 if !got.NoProxy {
60 t.Fatal("saved provider no_proxy = false, want true")
61 }
62 view := providerViewFromEntry(*got, false, true)
63 if !view.NoProxy {
64 t.Fatal("provider view noProxy = false, want true")
65 }
66 if spec := cfg.NetworkProxySpec(); !slices.Contains(spec.DirectHosts, "gw.example.internal") {
67 t.Fatalf("NetworkProxySpec.DirectHosts = %v, want the no_proxy provider host", spec.DirectHosts)
68 }
69 }
70
71 // TestWithProbeDirectHostMirrorsProviderNoProxy covers the unsaved-editor probe
72 // path: refreshing models for a no_proxy provider must bypass the proxy even
73 // before the entry is persisted, and a custom proxy mode must still win.
74 func TestWithProbeDirectHostMirrorsProviderNoProxy(t *testing.T) {
75 auto := netclient.ProxySpec{Mode: netclient.ModeAuto, DirectHosts: []string{"preset.example.cn"}}
76 got := withProbeDirectHost(auto, "https://gw.example.internal/v1", true)
77 if !slices.Contains(got.DirectHosts, "gw.example.internal") {
78 t.Fatalf("probe DirectHosts = %v, want the edited provider host", got.DirectHosts)
79 }
80 if !slices.Contains(got.DirectHosts, "preset.example.cn") {
81 t.Fatalf("probe DirectHosts = %v, lost existing entries", got.DirectHosts)
82 }
83
84 custom := netclient.ProxySpec{Mode: netclient.ModeCustom, URL: "http://corp-proxy.internal:3128"}
85 if got := withProbeDirectHost(custom, "https://gw.example.internal/v1", true); len(got.DirectHosts) != 0 {
86 t.Fatalf("custom proxy must override provider no_proxy, got DirectHosts %v", got.DirectHosts)
87 }
88
89 direct := withProbeDirectHost(auto, "https://gw.example.internal/v1", false)
90 if slices.Contains(direct.DirectHosts, "gw.example.internal") {
91 t.Fatalf("provider without no_proxy must stay proxied, got DirectHosts %v", direct.DirectHosts)
92 }
93
94 ipv6 := withProbeDirectHost(auto, "https://[2001:db8::1]:8443/v1", true)
95 if !slices.Contains(ipv6.DirectHosts, "2001:db8::1") {
96 t.Fatalf("IPv6 provider host = %v, want brackets and port removed", ipv6.DirectHosts)
97 }
98 }
99
99 lines GO