mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-10 03:07:41 +08:00
151 lines
5.0 KiB
Go
151 lines
5.0 KiB
Go
package completionruntime
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"ds2api/internal/account"
|
|
"ds2api/internal/auth"
|
|
"ds2api/internal/config"
|
|
"ds2api/internal/httpapi/openai/shared"
|
|
)
|
|
|
|
func TestExecuteStreamWithRetryUsesSharedRetryPayloadAndUsagePrompt(t *testing.T) {
|
|
ds := &fakeDeepSeekCaller{responses: []*http.Response{
|
|
sseHTTPResponse(http.StatusOK, `data: {"p":"response/content","v":"ok"}`),
|
|
}}
|
|
initial := sseHTTPResponse(http.StatusOK, `data: {"response_message_id":77,"p":"response/thinking_content","v":"plan"}`)
|
|
payload := map[string]any{"prompt": "original prompt"}
|
|
attemptsSeen := 0
|
|
retryPrompt := ""
|
|
|
|
ExecuteStreamWithRetry(context.Background(), ds, &auth.RequestAuth{}, initial, payload, "pow", StreamRetryOptions{
|
|
Surface: "test.stream",
|
|
Stream: true,
|
|
RetryEnabled: true,
|
|
UsagePrompt: "original prompt",
|
|
}, StreamRetryHooks{
|
|
ConsumeAttempt: func(resp *http.Response, allowDeferEmpty bool) (bool, bool) {
|
|
defer func() {
|
|
if err := resp.Body.Close(); err != nil {
|
|
t.Fatalf("close failed: %v", err)
|
|
}
|
|
}()
|
|
_, _ = io.ReadAll(resp.Body)
|
|
attemptsSeen++
|
|
return attemptsSeen == 2, attemptsSeen == 1 && allowDeferEmpty
|
|
},
|
|
ParentMessageID: func() int {
|
|
return 77
|
|
},
|
|
OnRetryPrompt: func(prompt string) {
|
|
retryPrompt = prompt
|
|
},
|
|
})
|
|
|
|
if attemptsSeen != 2 {
|
|
t.Fatalf("expected two stream attempts, got %d", attemptsSeen)
|
|
}
|
|
if len(ds.payloads) != 1 {
|
|
t.Fatalf("expected one retry completion call, got %d", len(ds.payloads))
|
|
}
|
|
if got := ds.payloads[0]["parent_message_id"]; got != 77 {
|
|
t.Fatalf("retry parent_message_id mismatch: %#v", got)
|
|
}
|
|
if prompt, _ := ds.payloads[0]["prompt"].(string); !strings.Contains(prompt, shared.EmptyOutputRetrySuffix) {
|
|
t.Fatalf("expected retry suffix in payload prompt, got %q", prompt)
|
|
}
|
|
if !strings.Contains(retryPrompt, shared.EmptyOutputRetrySuffix) {
|
|
t.Fatalf("expected retry suffix in usage prompt, got %q", retryPrompt)
|
|
}
|
|
}
|
|
|
|
func TestExecuteStreamWithRetrySwitchesManagedAccountBeforeFinal429(t *testing.T) {
|
|
t.Setenv("DS2API_CONFIG_JSON", `{
|
|
"keys":["managed-key"],
|
|
"accounts":[
|
|
{"email":"acc1@test.com","password":"pwd"},
|
|
{"email":"acc2@test.com","password":"pwd"}
|
|
]
|
|
}`)
|
|
store := config.LoadStore()
|
|
resolver := auth.NewResolver(store, account.NewPool(store), func(_ context.Context, acc config.Account) (string, error) {
|
|
return "token-" + acc.Identifier(), nil
|
|
})
|
|
req, _ := http.NewRequest(http.MethodPost, "/", nil)
|
|
req.Header.Set("Authorization", "Bearer managed-key")
|
|
a, err := resolver.Determine(req)
|
|
if err != nil {
|
|
t.Fatalf("determine failed: %v", err)
|
|
}
|
|
defer resolver.Release(a)
|
|
|
|
ds := &fakeDeepSeekCaller{
|
|
sessionByAccount: true,
|
|
responses: []*http.Response{
|
|
sseHTTPResponse(http.StatusOK, `data: {"response_message_id":12,"p":"response/thinking_content","v":"retry empty"}`),
|
|
sseHTTPResponse(http.StatusOK, `data: {"response_message_id":21,"p":"response/content","v":"ok from second account"}`),
|
|
},
|
|
}
|
|
initial := sseHTTPResponse(http.StatusOK, `data: {"response_message_id":11,"p":"response/thinking_content","v":"first empty"}`)
|
|
payload := map[string]any{"prompt": "original prompt", "chat_session_id": "session-acc1@test.com"}
|
|
attemptsSeen := 0
|
|
switchedSession := ""
|
|
|
|
ExecuteStreamWithRetry(context.Background(), ds, a, initial, payload, "pow", StreamRetryOptions{
|
|
Surface: "test.stream",
|
|
Stream: true,
|
|
RetryEnabled: true,
|
|
RetryMaxAttempts: 1,
|
|
UsagePrompt: "original prompt",
|
|
}, StreamRetryHooks{
|
|
ConsumeAttempt: func(resp *http.Response, allowDeferEmpty bool) (bool, bool) {
|
|
defer func() {
|
|
if err := resp.Body.Close(); err != nil {
|
|
t.Fatalf("close failed: %v", err)
|
|
}
|
|
}()
|
|
body, _ := io.ReadAll(resp.Body)
|
|
attemptsSeen++
|
|
if strings.Contains(string(body), "ok from second account") {
|
|
return true, false
|
|
}
|
|
if !allowDeferEmpty {
|
|
t.Fatalf("expected empty attempt %d to be deferred before final 429", attemptsSeen)
|
|
}
|
|
return false, true
|
|
},
|
|
ParentMessageID: func() int {
|
|
return 11 + attemptsSeen
|
|
},
|
|
OnAccountSwitch: func(sessionID string) {
|
|
switchedSession = sessionID
|
|
},
|
|
})
|
|
|
|
if attemptsSeen != 3 {
|
|
t.Fatalf("expected three stream attempts, got %d", attemptsSeen)
|
|
}
|
|
if switchedSession != "session-acc2@test.com" {
|
|
t.Fatalf("expected switched session id, got %q", switchedSession)
|
|
}
|
|
wantAccounts := []string{"acc1@test.com", "acc2@test.com"}
|
|
if len(ds.completionAccounts) != len(wantAccounts) {
|
|
t.Fatalf("completion accounts mismatch: got %v want %v", ds.completionAccounts, wantAccounts)
|
|
}
|
|
for i, want := range wantAccounts {
|
|
if ds.completionAccounts[i] != want {
|
|
t.Fatalf("completion account %d = %q want %q (all=%v)", i, ds.completionAccounts[i], want, ds.completionAccounts)
|
|
}
|
|
}
|
|
if got := ds.payloads[1]["chat_session_id"]; got != "session-acc2@test.com" {
|
|
t.Fatalf("switched payload session mismatch: %#v", got)
|
|
}
|
|
if prompt, _ := ds.payloads[1]["prompt"].(string); strings.Contains(prompt, shared.EmptyOutputRetrySuffix) {
|
|
t.Fatalf("expected switched-account prompt without empty-output suffix, got %q", prompt)
|
|
}
|
|
}
|