Files
openteam/server/internal/proxy/stream_test.go
T
SakurasanandClaude ec4de8d913 M0-M4: 推倒重来基线(基建+用户/密钥/核心代理+前端+管理后台+三协议互转)
- 后端 Go+Gin+GORM: 配置(OT_ env)/SQLite/Postgres 双驱动、用户体系(argon2id+JWT access/refresh)、
  API Key(sk- 48位, 仅存 SHA-256 哈希)
- 代理网关: /v1/chat/completions、/v1/responses、/v1/messages、/v1/models;错误按客户端协议返回
- 三协议互转(convert 包): Chat↔Messages↔Responses 请求/响应 + 流式 SSE 逐事件转换(直通优先)
- 用量计费: 异步批量记账、余额扣减、balance_logs、usage_daily 日聚合
- 管理 API: 用户/渠道 CRUD+测试+模型导入/模型定价+绑定/统计/系统配置
- 前端 Vue3+TS+Tailwind(taste-skill 设计 tokens): Landing/登录注册/控制台/管理后台,
  自建组件+Phosphor 图标+自建 SVG 趋势图, 已过 web-design-guidelines 复查
- mock 上游: OpenAI+Anthropic 双协议模拟(含流式)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-15 15:34:06 +08:00

126 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package proxy
import (
"encoding/json"
"io"
"strings"
"testing"
)
func TestSSEScannerSplitsLines(t *testing.T) {
input := "event: message\ndata: {\"a\":1}\n\n" +
"data: {\"b\":2}\r\n\r\n" +
"data: [DONE]\n\n"
s := newSSEScanner(strings.NewReader(input))
var lines []string
for {
line, err := s.Next()
if line != nil {
lines = append(lines, string(line))
}
if err == io.EOF {
break
}
if err != nil {
t.Fatalf("Next: %v", err)
}
}
want := []string{
"event: message\n",
"data: {\"a\":1}\n",
"\n",
"data: {\"b\":2}\r\n",
"\r\n",
"data: [DONE]\n",
"\n",
}
if len(lines) != len(want) {
t.Fatalf("line count = %d, want %d (lines: %q)", len(lines), len(want), lines)
}
for i := range want {
if lines[i] != want[i] {
t.Fatalf("line[%d] = %q, want %q", i, lines[i], want[i])
}
}
}
func TestScanUsageChatStream(t *testing.T) {
chunk := `data: {"id":"x","choices":[],"usage":{"prompt_tokens":12,"completion_tokens":9,"total_tokens":21}}`
raw := scanUsage([]byte(chunk + "\n"))
if raw == nil {
t.Fatal("expected usage extracted")
}
var us usageShape
if err := json.Unmarshal(raw, &us); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if us.PromptTokens != 12 || us.CompletionTokens != 9 {
t.Fatalf("usage mismatch: %+v", us)
}
}
func TestScanUsageResponsesCompleted(t *testing.T) {
line := `data: {"type":"response.completed","response":{"id":"r1","status":"completed","usage":{"input_tokens":15,"output_tokens":11}}}`
raw := scanUsage([]byte(line + "\n"))
if raw == nil {
t.Fatal("expected usage extracted from response.completed")
}
var us usageShape
_ = json.Unmarshal(raw, &us)
if us.InputTokens != 15 || us.OutputTokens != 11 {
t.Fatalf("usage mismatch: %+v", us)
}
}
func TestScanUsageIgnoresNonUsage(t *testing.T) {
if raw := scanUsage([]byte(`data: {"type":"response.output_text.delta","delta":"hi"}`)); raw != nil {
t.Fatalf("expected nil for non-usage line, got %s", raw)
}
if raw := scanUsage([]byte(`data: [DONE]`)); raw != nil {
t.Fatal("expected nil for [DONE]")
}
}
func TestExtractUsageChatBody(t *testing.T) {
body := `{"id":"x","choices":[{"message":{"role":"assistant","content":"hi"}}],"usage":{"prompt_tokens":1,"completion_tokens":2,"total_tokens":3}}`
raw := extractUsage([]byte(body))
if raw == nil {
t.Fatal("expected usage")
}
if !strings.Contains(string(raw), `"prompt_tokens":1`) {
t.Fatalf("unexpected usage: %s", raw)
}
}
func TestExtractUsageResponsesNested(t *testing.T) {
// responses 顶层只有 response 对象,usage 嵌套其中
body := `{"id":"r1","object":"response","status":"completed","response":{"usage":{"input_tokens":7,"output_tokens":8}}}`
raw := extractUsage([]byte(body))
if raw == nil {
t.Fatal("expected nested usage")
}
var us usageShape
_ = json.Unmarshal(raw, &us)
if us.InputTokens != 7 || us.OutputTokens != 8 {
t.Fatalf("usage mismatch: %+v", us)
}
}
func TestUsageSinkMergesFields(t *testing.T) {
// message_start 给 input,message_delta 给 output,合并后两者都在
s := &usageSink{}
s.push(json.RawMessage(`{"input_tokens":14,"output_tokens":0}`))
s.push(json.RawMessage(`{"output_tokens":10}`))
got := s.Shape()
if got.InputTokens != 14 || got.OutputTokens != 10 {
t.Fatalf("merge mismatch: %+v", got)
}
// chat 末块同时携带两字段
s2 := &usageSink{}
s2.push(json.RawMessage(`{"prompt_tokens":12,"completion_tokens":9}`))
g := s2.Shape()
if g.PromptTokens != 12 || g.CompletionTokens != 9 {
t.Fatalf("chat usage mismatch: %+v", g)
}
}