返回 DeepSeek-Reasonix
model_settings_retry_test.go
根目录 / internal / boot / model_settings_retry_test.go
1 package boot
2
3 import (
4 "context"
5 "fmt"
6 "io"
7 "net/http"
8 "net/http/httptest"
9 "sync"
10 "sync/atomic"
11 "testing"
12 "time"
13
14 "reasonix/internal/config"
15 "reasonix/internal/event"
16 )
17
18 func TestModelSettingsHTTPRetryKeepsAcceptedCredential(t *testing.T) {
19 isolateConfigHome(t)
20 root := robustTempDir(t)
21 t.Chdir(root)
22 ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
23 defer cancel()
24 entered, release := make(chan struct{}), make(chan struct{})
25 var unblock sync.Once
26 t.Cleanup(func() { unblock.Do(func() { close(release) }) })
27 var calls atomic.Int32
28 keys := make(chan string, 8)
29 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
30 _, _ = io.Copy(io.Discard, r.Body)
31 keys <- r.Header.Get("Authorization")
32 if calls.Add(1) == 1 {
33 close(entered)
34 select {
35 case <-release:
36 case <-r.Context().Done():
37 return
38 }
39 http.Error(w, "temporarily unavailable", http.StatusServiceUnavailable)
40 return
41 }
42 w.Header().Set("Content-Type", "text/event-stream")
43 fmt.Fprint(w, "data: {\"choices\":[{\"delta\":{\"content\":\"recovered\"},\"finish_reason\":\"stop\"}]}\n\ndata: [DONE]\n\n")
44 }))
45 defer server.Close()
46 if _, err := config.SetCredential("RETRY_SNAPSHOT_KEY", "before-save"); err != nil {
47 t.Fatal(err)
48 }
49 cfg := config.Default()
50 cfg.DefaultModel = "p/m"
51 cfg.Providers = []config.ProviderEntry{{Name: "p", Kind: "openai", BaseURL: server.URL, Model: "m", APIKeyEnv: "RETRY_SNAPSHOT_KEY"}}
52 if err := cfg.SaveTo(config.UserConfigPath()); err != nil {
53 t.Fatal(err)
54 }
55 ctrl, err := Build(ctx, Options{WorkspaceRoot: root, Sink: event.Discard})
56 if err != nil {
57 t.Fatal(err)
58 }
59 defer ctrl.Close()
60 done := make(chan error, 1)
61 go func() { done <- ctrl.RunTurn(ctx, "retry this request") }()
62 select {
63 case <-entered:
64 case <-ctx.Done():
65 t.Fatal(ctx.Err())
66 }
67 if _, err := config.SetCredential("RETRY_SNAPSHOT_KEY", "after-save"); err != nil {
68 t.Fatal(err)
69 }
70 unblock.Do(func() { close(release) })
71 select {
72 case err := <-done:
73 if err != nil {
74 t.Fatal(err)
75 }
76 case <-ctx.Done():
77 t.Fatal(ctx.Err())
78 }
79 if calls.Load() != 2 {
80 t.Fatalf("expected failed request and its successful retry, got %d", calls.Load())
81 }
82 for range 2 {
83 if key := <-keys; key != "Bearer before-save" {
84 t.Fatal("HTTP retry crossed credential generations")
85 }
86 }
87 next, err := Build(ctx, Options{WorkspaceRoot: root, Sink: event.Discard})
88 if err != nil {
89 t.Fatal(err)
90 }
91 defer next.Close()
92 if err := next.RunTurn(ctx, "next request"); err != nil {
93 t.Fatal(err)
94 }
95 if key := <-keys; key != "Bearer after-save" {
96 t.Fatal("next runtime did not use the saved credential")
97 }
98 }
99
99 lines GO