- Chat 接口拒绝 n>1(此前静默丢弃多余 choice),返回 400 结构化错误 - Responses 接口透传 text.format(json_schema / json_object),三种接口协议对齐 - TLS 指纹统一升级到 Chrome_150(internal/httpx.DefaultProfile),与 UA 匹配 - metrics 落盘失败不再静默:Save() 返回错误并打日志,临时文件失败会清理 - GET /api/config 脱敏 admin.password 与 turnstile.api_key(控制台从不展示, 不应随 5s 轮询下发);PUT 改走 ReplaceJSON,先校验后替换、失败回滚 - set_limits / set_turnstile 改指针部分更新:缺字段保持现状,不会把并发、 限流或求解开关按零值清掉 - 读请求体统一 64MB 上限;配置写盘原子替换 + 0600 权限 - main.go 改为 run() 返回退出码:日志落盘、指标落盘、xray 子进程回收全部 挂 defer,任何退出路径(含启动失败)都会执行,杜绝孤儿 xray - proxypool / turnstile / upstream / openai / config / httpx / logbuf 等 逐项补红绿回归测试,新增 14 个测试文件,race 下全绿
62 lines
1.8 KiB
Go
62 lines
1.8 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/nyoungo/dsfree2api/internal/config"
|
|
)
|
|
|
|
func newMiddlewareServer(t *testing.T) *Server {
|
|
t.Helper()
|
|
cfg, err := config.Load(examplePath)
|
|
if err != nil {
|
|
t.Fatalf("load config: %v", err)
|
|
}
|
|
return newTestServerWithConfig(t, cfg)
|
|
}
|
|
|
|
// 回归:SSE 已经开流(200 + 若干字节)之后再 panic,不能又写一个 500 的
|
|
// JSON 进响应体 —— 状态码改不了,只会把 {"error"...} 拼在事件流后面,
|
|
// 客户端解析流直接失败。
|
|
func TestRecoverPanicAfterHeadersKeepsBodyIntact(t *testing.T) {
|
|
s := newMiddlewareServer(t)
|
|
h := s.recoverPanic(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte("data: {\"a\":1}\n\n"))
|
|
panic("boom")
|
|
}))
|
|
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/v1/chat/completions", nil))
|
|
|
|
body := w.Body.String()
|
|
if strings.Contains(body, "internal server error") {
|
|
t.Fatalf("已开流的响应被追加了 500 JSON: %q", body)
|
|
}
|
|
if !strings.Contains(body, `data: {"a":1}`) {
|
|
t.Fatalf("原有事件流丢了: %q", body)
|
|
}
|
|
}
|
|
|
|
// 回归:还没发出响应头就 panic,必须照常返回 500 + 错误对象。
|
|
func TestRecoverPanicBeforeHeadersWrites500(t *testing.T) {
|
|
s := newMiddlewareServer(t)
|
|
h := s.recoverPanic(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
panic("boom")
|
|
}))
|
|
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/v1/chat/completions", nil))
|
|
|
|
if w.Code != http.StatusInternalServerError {
|
|
t.Fatalf("status = %d, want 500", w.Code)
|
|
}
|
|
if !strings.Contains(w.Body.String(), "internal server error") {
|
|
t.Fatalf("body = %q, want the error object", w.Body.String())
|
|
}
|
|
}
|