File size: 4,190 Bytes
8d3471e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package chat

import (
	"context"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"ds2api/internal/auth"
	dsclient "ds2api/internal/deepseek/client"
)

type autoDeleteModeDSStub struct {
	resp          *http.Response
	singleCalls   int
	allCalls      int
	lastSessionID string
	lastCtxErr    error
}

func (m *autoDeleteModeDSStub) CreateSession(_ context.Context, _ *auth.RequestAuth, _ int) (string, error) {
	return "session-id", nil
}

func (m *autoDeleteModeDSStub) GetPow(_ context.Context, _ *auth.RequestAuth, _ int) (string, error) {
	return "pow", nil
}

func (m *autoDeleteModeDSStub) UploadFile(_ context.Context, _ *auth.RequestAuth, _ dsclient.UploadFileRequest, _ int) (*dsclient.UploadFileResult, error) {
	return &dsclient.UploadFileResult{ID: "file-id", Filename: "file.txt", Bytes: 1, Status: "uploaded"}, nil
}

func (m *autoDeleteModeDSStub) CallCompletion(_ context.Context, _ *auth.RequestAuth, _ map[string]any, _ string, _ int) (*http.Response, error) {
	return m.resp, nil
}

func (m *autoDeleteModeDSStub) DeleteSessionForToken(_ context.Context, _ string, sessionID string) (*dsclient.DeleteSessionResult, error) {
	m.singleCalls++
	m.lastSessionID = sessionID
	return &dsclient.DeleteSessionResult{SessionID: sessionID, Success: true}, nil
}

func (m *autoDeleteModeDSStub) DeleteAllSessionsForToken(_ context.Context, _ string) error {
	m.allCalls++
	return nil
}

func (m *autoDeleteModeDSStub) DeleteSessionForTokenCtx(ctx context.Context, _ string, sessionID string) (*dsclient.DeleteSessionResult, error) {
	m.singleCalls++
	m.lastSessionID = sessionID
	m.lastCtxErr = ctx.Err()
	return &dsclient.DeleteSessionResult{SessionID: sessionID, Success: true}, nil
}

func TestChatCompletionsAutoDeleteModes(t *testing.T) {
	tests := []struct {
		name       string
		mode       string
		wantSingle int
		wantAll    int
	}{
		{name: "none", mode: "none"},
		{name: "single", mode: "single", wantSingle: 1},
		{name: "all", mode: "all", wantAll: 1},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			ds := &autoDeleteModeDSStub{
				resp: makeOpenAISSEHTTPResponse(
					`data: {"p":"response/content","v":"hello"}`,
					"data: [DONE]",
				),
			}
			h := &Handler{
				Store: mockOpenAIConfig{
					autoDeleteMode: tc.mode,
				},
				Auth: streamStatusAuthStub{},
				DS:   ds,
			}

			reqBody := `{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"hi"}],"stream":false}`
			req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(reqBody))
			req.Header.Set("Authorization", "Bearer direct-token")
			req.Header.Set("Content-Type", "application/json")
			rec := httptest.NewRecorder()

			h.ChatCompletions(rec, req)

			if rec.Code != http.StatusOK {
				t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
			}
			if ds.singleCalls != tc.wantSingle {
				t.Fatalf("single delete calls=%d want=%d", ds.singleCalls, tc.wantSingle)
			}
			if ds.allCalls != tc.wantAll {
				t.Fatalf("all delete calls=%d want=%d", ds.allCalls, tc.wantAll)
			}
			if tc.wantSingle > 0 && ds.lastSessionID != "session-id" {
				t.Fatalf("expected single delete for session-id, got %q", ds.lastSessionID)
			}
		})
	}
}

type autoDeleteCtxDSStub struct {
	autoDeleteModeDSStub
}

func (m *autoDeleteCtxDSStub) DeleteSessionForToken(ctx context.Context, token string, sessionID string) (*dsclient.DeleteSessionResult, error) {
	return m.DeleteSessionForTokenCtx(ctx, token, sessionID)
}

func (m *autoDeleteCtxDSStub) DeleteAllSessionsForToken(_ context.Context, _ string) error {
	m.allCalls++
	return nil
}

func TestAutoDeleteRemoteSessionIgnoresCanceledParentContext(t *testing.T) {
	ds := &autoDeleteCtxDSStub{}
	h := &Handler{
		Store: mockOpenAIConfig{
			autoDeleteMode: "single",
		},
		DS: ds,
	}
	a := &auth.RequestAuth{DeepSeekToken: "token", AccountID: "acct"}
	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	h.autoDeleteRemoteSession(ctx, a, "session-id")

	if ds.singleCalls != 1 {
		t.Fatalf("single delete calls=%d want=1", ds.singleCalls)
	}
	if ds.lastCtxErr != nil {
		t.Fatalf("delete ctx should not inherit cancellation, got %v", ds.lastCtxErr)
	}
}