返回 DeepSeek-Reasonix
model_settings_approval_test.go
根目录 / internal / boot / model_settings_approval_test.go
1 package boot
2
3 import (
4 "context"
5 "fmt"
6 "io"
7 "net/http"
8 "net/http/httptest"
9 "os"
10 "path/filepath"
11 "sync/atomic"
12 "testing"
13 "time"
14
15 "reasonix/internal/config"
16 "reasonix/internal/event"
17 )
18
19 func TestModelSettingsApprovalResumeKeepsAcceptedCredential(t *testing.T) {
20 isolateConfigHome(t)
21 root := robustTempDir(t)
22 t.Chdir(root)
23 ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
24 defer cancel()
25 var calls atomic.Int32
26 keys := make(chan string, 4)
27 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
28 _, _ = io.Copy(io.Discard, r.Body)
29 keys <- r.Header.Get("Authorization")
30 w.Header().Set("Content-Type", "text/event-stream")
31 if calls.Add(1) == 1 {
32 fmt.Fprint(w, "data: {\"choices\":[{\"delta\":{\"tool_calls\":[{\"index\":0,\"id\":\"write-approval\",\"type\":\"function\",\"function\":{\"name\":\"write_file\",\"arguments\":\"{\\\"path\\\":\\\"approved.txt\\\",\\\"content\\\":\\\"approved snapshot\\\"}\"}}]},\"finish_reason\":\"tool_calls\"}]}\n\ndata: [DONE]\n\n")
33 return
34 }
35 fmt.Fprint(w, "data: {\"choices\":[{\"delta\":{\"content\":\"finished\"},\"finish_reason\":\"stop\"}]}\n\ndata: [DONE]\n\n")
36 }))
37 defer server.Close()
38 if _, err := config.SetCredential("APPROVAL_SNAPSHOT_KEY", "before-save"); err != nil {
39 t.Fatal(err)
40 }
41 cfg := config.Default()
42 cfg.DefaultModel = "p/m"
43 cfg.Providers = []config.ProviderEntry{{Name: "p", Kind: "openai", BaseURL: server.URL, Model: "m", APIKeyEnv: "APPROVAL_SNAPSHOT_KEY"}}
44 cfg.Permissions.Mode, cfg.Permissions.Ask = "ask", []string{"write_file"}
45 if err := cfg.SaveTo(config.UserConfigPath()); err != nil {
46 t.Fatal(err)
47 }
48 approval := make(chan string, 1)
49 ctrl, err := Build(ctx, Options{WorkspaceRoot: root, Sink: event.FuncSink(func(e event.Event) {
50 if e.Kind == event.ApprovalRequest {
51 approval <- e.Approval.ID
52 }
53 })})
54 if err != nil {
55 t.Fatal(err)
56 }
57 defer ctrl.Close()
58 ctrl.EnableInteractiveApproval()
59 done := make(chan error, 1)
60 go func() { done <- ctrl.RunTurn(ctx, "write approved.txt") }()
61 var id string
62 select {
63 case id = <-approval:
64 case <-ctx.Done():
65 t.Fatal(ctx.Err())
66 }
67 if _, err := config.SetCredential("APPROVAL_SNAPSHOT_KEY", "after-save"); err != nil {
68 t.Fatal(err)
69 }
70 applied, desired, err := ctrl.ModelSettingsState()
71 if err != nil || applied == desired {
72 t.Fatal("save did not mark the pending approval runtime stale")
73 }
74 ctrl.Approve(id, true, false, false)
75 select {
76 case err := <-done:
77 if err != nil {
78 t.Fatal(err)
79 }
80 case <-ctx.Done():
81 t.Fatal(ctx.Err())
82 }
83 for range 2 {
84 select {
85 case key := <-keys:
86 if key != "Bearer before-save" {
87 t.Fatal("approval resume crossed credential generations")
88 }
89 case <-ctx.Done():
90 t.Fatal(ctx.Err())
91 }
92 }
93 content, err := os.ReadFile(filepath.Join(root, "approved.txt"))
94 if err != nil || string(content) != "approved snapshot" {
95 t.Fatalf("approved tool did not finish: %v", err)
96 }
97 }
98
98 lines GO