返回 DeepSeek-Reasonix
serve_provider_setup_test.go
根目录 / internal / cli / serve_provider_setup_test.go
1 package cli
2
3 import (
4 "bytes"
5 "io"
6 "net/http"
7 "net/http/cookiejar"
8 "net/http/httptest"
9 "os"
10 "os/exec"
11 "path/filepath"
12 "strings"
13 "testing"
14 "time"
15 )
16
17 const serveMissingKeyHelperEnv = "REASONIX_TEST_SERVE_MISSING_KEY_HELPER"
18
19 // TestServeStartsWithMissingProviderKey exercises the real runServe lifecycle:
20 // the listener and port file must become available before a Provider key exists,
21 // and the authenticated browser surface must show the setup page.
22 func TestServeStartsWithMissingProviderKey(t *testing.T) {
23 if os.Getenv(serveMissingKeyHelperEnv) == "1" {
24 // The package TestMain clears path overrides before dispatching tests, so
25 // restore this helper's isolated home after that process-wide guard runs.
26 if err := os.Setenv("REASONIX_HOME", os.Getenv("REASONIX_TEST_SERVE_HOME")); err != nil {
27 os.Exit(2)
28 }
29 code := runServe([]string{
30 "--model", "remote-demo/model-a",
31 "--addr", "127.0.0.1:0",
32 "--port-file", os.Getenv("REASONIX_TEST_SERVE_PORT_FILE"),
33 "--auth", "token",
34 "--token", "serve-setup-test-token",
35 })
36 os.Exit(code)
37 }
38
39 home := t.TempDir()
40 balanceStarted := make(chan struct{})
41 releaseBalance := make(chan struct{})
42 balanceServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
43 select {
44 case <-balanceStarted:
45 default:
46 close(balanceStarted)
47 }
48 <-releaseBalance
49 w.Header().Set("Content-Type", "application/json")
50 _, _ = w.Write([]byte(`{}`))
51 }))
52 t.Cleanup(balanceServer.Close)
53 t.Cleanup(func() { close(releaseBalance) })
54 configPath := filepath.Join(home, "config.toml")
55 configBody := `default_model = "remote-demo/model-a"
56
57 [[providers]]
58 name = "remote-demo"
59 kind = "openai"
60 base_url = "https://example.invalid/v1"
61 balance_url = "` + balanceServer.URL + `"
62 models = ["model-a"]
63 default = "model-a"
64 api_key_env = "REASONIX_TEST_REMOTE_MISSING_KEY"
65 `
66 if err := os.WriteFile(configPath, []byte(configBody), 0o600); err != nil {
67 t.Fatal(err)
68 }
69 portFile := filepath.Join(home, "serve.addr")
70 cmd := exec.Command(os.Args[0], "-test.run=^TestServeStartsWithMissingProviderKey$")
71 cmd.Env = replaceServeTestEnv(os.Environ(),
72 serveMissingKeyHelperEnv+"=1",
73 "REASONIX_HOME="+home,
74 "REASONIX_TEST_SERVE_HOME="+home,
75 "REASONIX_CREDENTIALS_STORE=file",
76 "REASONIX_TEST_REMOTE_MISSING_KEY=",
77 "REASONIX_TEST_SERVE_PORT_FILE="+portFile,
78 )
79 var output bytes.Buffer
80 cmd.Stdout = &output
81 cmd.Stderr = &output
82 if err := cmd.Start(); err != nil {
83 t.Fatal(err)
84 }
85 t.Cleanup(func() {
86 if cmd.Process != nil {
87 _ = cmd.Process.Kill()
88 _ = cmd.Wait()
89 }
90 })
91
92 var addr string
93 deadline := time.Now().Add(15 * time.Second)
94 for time.Now().Before(deadline) {
95 data, err := os.ReadFile(portFile)
96 if err == nil && strings.TrimSpace(string(data)) != "" {
97 addr = strings.TrimSpace(string(data))
98 break
99 }
100 if err != nil && !os.IsNotExist(err) {
101 t.Fatalf("read Serve port file: %v", err)
102 }
103 time.Sleep(20 * time.Millisecond)
104 }
105 if addr == "" {
106 t.Fatalf("Serve did not publish its port with a missing Provider key:\n%s", output.String())
107 }
108 select {
109 case <-balanceStarted:
110 case <-time.After(5 * time.Second):
111 t.Fatalf("Serve did not start the configured balance diagnostic:\n%s", output.String())
112 }
113
114 jar, err := cookiejar.New(nil)
115 if err != nil {
116 t.Fatal(err)
117 }
118 client := &http.Client{Jar: jar, Timeout: 5 * time.Second}
119 resp, err := client.Get("http://" + addr + "/?token=serve-setup-test-token")
120 if err != nil {
121 t.Fatalf("open missing-key Serve: %v\n%s", err, output.String())
122 }
123 body, err := io.ReadAll(resp.Body)
124 resp.Body.Close()
125 if err != nil {
126 t.Fatal(err)
127 }
128 if resp.StatusCode != http.StatusOK {
129 t.Fatalf("missing-key Serve status = %d, want 200: %s\n%s", resp.StatusCode, body, output.String())
130 }
131 if !bytes.Contains(body, []byte("Reasonix Provider Setup")) {
132 t.Fatalf("missing-key Serve did not show setup page:\n%s", body)
133 }
134 }
135
136 func replaceServeTestEnv(base []string, overrides ...string) []string {
137 keys := make(map[string]bool, len(overrides))
138 for _, item := range overrides {
139 key, _, _ := strings.Cut(item, "=")
140 keys[key] = true
141 }
142 out := make([]string, 0, len(base)+len(overrides))
143 for _, item := range base {
144 key, _, _ := strings.Cut(item, "=")
145 if !keys[key] {
146 out = append(out, item)
147 }
148 }
149 return append(out, overrides...)
150 }
151
151 lines GO