返回 DeepSeek-Reasonix
provider_cache_integration_test.go
根目录 / internal / session / provider_cache_integration_test.go
1 package session_test
2
3 import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "io"
8 "net/http"
9 "net/http/httptest"
10 "path/filepath"
11 "testing"
12
13 "reasonix/internal/provider"
14 "reasonix/internal/provider/anthropic"
15 "reasonix/internal/provider/openai"
16 "reasonix/internal/provider/responses"
17 "reasonix/internal/session"
18 )
19
20 func TestProviderRequestBytesSurviveSessionV4RoundTrip(t *testing.T) {
21 messages := []provider.Message{
22 {ID: "system", Role: provider.RoleSystem, Content: "stable system"},
23 {ID: "user", Role: provider.RoleUser, Content: string(bytes.Repeat([]byte("cache-prefix-"), 7000))},
24 {ID: "assistant", Role: provider.RoleAssistant, Content: "calling", ReasoningContent: "reason", ReasoningID: "reason-1", ReasoningStatus: "completed", ReasoningSignature: "opaque-signature", ToolCalls: []provider.ToolCall{{ID: "call-1", Name: "lookup", Arguments: `{"q":"value"}`, ThoughtSignature: "thought-proof"}}},
25 {ID: "tool", Role: provider.RoleTool, ToolCallID: "call-1", Name: "lookup", Content: "result"},
26 }
27 after := persistCacheMessages(t, messages)
28 tools := []provider.ToolSchema{{Name: "lookup", Description: "look up", Parameters: json.RawMessage(`{"type":"object","properties":{"q":{"type":"string"}}}`)}}
29
30 tests := []struct {
31 name string
32 new func(string) provider.Provider
33 }{
34 {name: "openai", new: func(url string) provider.Provider {
35 p, err := openai.New(provider.Config{Name: "openai", BaseURL: url, Model: "cache-model", APIKey: "test"})
36 if err != nil {
37 t.Fatal(err)
38 }
39 return p
40 }},
41 {name: "anthropic", new: func(url string) provider.Provider {
42 p, err := anthropic.New(provider.Config{Name: "anthropic", BaseURL: url, Model: "cache-model", APIKey: "test"})
43 if err != nil {
44 t.Fatal(err)
45 }
46 return p
47 }},
48 {name: "responses", new: func(url string) provider.Provider {
49 return responses.New(responses.Config{Name: "responses", BaseURL: url, Model: "cache-model", APIKey: "test", Mode: "stateless"})
50 }},
51 }
52 for _, test := range tests {
53 t.Run(test.name, func(t *testing.T) {
54 var bodies [][]byte
55 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
56 body, _ := io.ReadAll(r.Body)
57 bodies = append(bodies, append([]byte(nil), body...))
58 w.Header().Set("Content-Type", "application/json")
59 w.WriteHeader(http.StatusBadRequest)
60 _, _ = io.WriteString(w, `{"error":{"message":"captured"}}`)
61 }))
62 defer server.Close()
63 p := test.new(server.URL)
64 captureProviderRequest(t, p, provider.Request{Messages: messages, Tools: tools})
65 captureProviderRequest(t, p, provider.Request{Messages: after, Tools: tools})
66 if len(bodies) != 2 || !bytes.Equal(bodies[0], bodies[1]) {
67 t.Fatalf("serialized request changed across v4 persistence: requests=%d\nbefore: %s\nafter: %s", len(bodies), firstBody(bodies, 0), firstBody(bodies, 1))
68 }
69 })
70 }
71 }
72
73 func captureProviderRequest(t *testing.T, p provider.Provider, request provider.Request) {
74 t.Helper()
75 stream, err := p.Stream(t.Context(), request)
76 if err != nil {
77 return
78 }
79 for range stream {
80 }
81 }
82
83 func firstBody(bodies [][]byte, index int) []byte {
84 if index < 0 || index >= len(bodies) {
85 return nil
86 }
87 return bodies[index]
88 }
89
90 func persistCacheMessages(t *testing.T, messages []provider.Message) []provider.Message {
91 t.Helper()
92 dir := filepath.Join(t.TempDir(), "cache")
93 store, err := session.CreateStore(dir, "cache")
94 if err != nil {
95 t.Fatal(err)
96 }
97 for _, message := range messages {
98 payload, err := json.Marshal(map[string]any{"message": message})
99 if err != nil {
100 t.Fatal(err)
101 }
102 if _, err := store.Append(t.Context(), session.Batch{OperationID: "message-" + message.ID, Events: []session.Event{{Kind: "message/complete", Payload: payload}}}); err != nil {
103 t.Fatal(err)
104 }
105 }
106 if _, err := store.Flush(t.Context()); err != nil {
107 t.Fatal(err)
108 }
109 if err := store.Close(t.Context()); err != nil {
110 t.Fatal(err)
111 }
112 reopened, err := session.Open(dir, "cache")
113 if err != nil {
114 t.Fatal(err)
115 }
116 defer reopened.Close(context.Background())
117 return reopened.DeriveMessages()
118 }
119
119 lines GO