返回 DeepSeek-Reasonix
session_transport_recovery_test.go
根目录 / internal / agent / session_transport_recovery_test.go
1 package agent
2
3 import (
4 "errors"
5 "io"
6 "net/http"
7 "net/http/httptest"
8 "os"
9 "path/filepath"
10 "strings"
11 "testing"
12
13 "reasonix/internal/event"
14 "reasonix/internal/provider"
15 "reasonix/internal/provider/openai"
16 )
17
18 func TestSessionReportsProviderFailuresWithoutRetry(t *testing.T) {
19 for _, history := range []string{"new", "legacy"} {
20 for _, connection := range []string{"valid", "invalid-header", "unavailable"} {
21 t.Run(history+"/"+connection, func(t *testing.T) {
22 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
23 if connection == "unavailable" {
24 w.Header().Set("Retry-After", "120")
25 w.WriteHeader(http.StatusServiceUnavailable)
26 _, _ = io.WriteString(w, `{"error":{"message":"temporarily unavailable"}}`)
27 return
28 }
29 w.Header().Set("Content-Type", "text/event-stream")
30 _, _ = io.WriteString(w, "data: {\"choices\":[{\"delta\":{\"content\":\"continued\"},\"finish_reason\":\"stop\"}]}\n\ndata: [DONE]\n\n")
31 }))
32 defer server.Close()
33 sess := NewSession("system")
34 if history == "legacy" {
35 path := filepath.Join(t.TempDir(), "legacy.jsonl")
36 if err := os.WriteFile(path, []byte(legacySessionFixture), 0o600); err != nil {
37 t.Fatal(err)
38 }
39 var err error
40 sess, err = LoadSession(path)
41 if err != nil {
42 t.Fatal(err)
43 }
44 }
45 cfg := provider.Config{
46 Name: "deepseek", Model: "deepseek-v4-flash", BaseURL: server.URL, HTTPClient: server.Client(),
47 Extra: map[string]any{"reasoning_protocol": "deepseek"},
48 }
49 if connection == "invalid-header" {
50 cfg.Extra["headers"] = map[string]string{"bad header": "value"}
51 }
52 p, err := openai.New(cfg)
53 if err != nil {
54 t.Fatal(err)
55 }
56 sink := &recordSink{}
57 a := New(p, echoRegistry(), sess, Options{}, sink)
58 err = a.Run(withNoClosedLoop(t.Context()), "continue")
59 if connection == "invalid-header" {
60 var failure *provider.RequestFailure
61 if !errors.As(err, &failure) || !strings.Contains(err.Error(), "invalid header field name") || provider.AsRecoveryWaitExhausted(err) != nil {
62 t.Fatalf("invalid request must preserve its cause without waiting: %v", err)
63 }
64 } else if connection == "unavailable" {
65 var failure *provider.APIError
66 if !errors.As(err, &failure) || failure.Status != http.StatusServiceUnavailable || provider.AsRecoveryWaitExhausted(err) != nil {
67 t.Fatalf("upstream error must return without waiting: %v", err)
68 }
69 } else if err != nil || len(sink.kinds(event.Text)) == 0 {
70 t.Fatalf("session failed to continue: %v", err)
71 }
72 if retries := sink.kinds(event.Retrying); len(retries) != 0 {
73 t.Fatalf("unexpected network recovery: %+v", retries)
74 }
75 })
76 }
77 }
78 }
79
79 lines GO