Files
weichen f3f23a3018 feat(errcode): 用稳定错误码记录 HTTP 与异步失败
失败响应此前不写日志,request_id 无法对照原因。注册表不覆盖处理器已经给出的 HTTP 状态。
2026-09-28 02:27:16 +00:00

91 lines
2.7 KiB
Go

package httpx
import (
"bytes"
"encoding/json"
"log/slog"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestWriteSanitizesInternalAndKeepsLegacyCode(t *testing.T) {
rec := httptest.NewRecorder()
Write(rec, http.StatusInternalServerError, CodeINTERNAL, "pq: relation foo leaked /home/weichen/secret", "req_test", nil)
var env Envelope
if err := json.Unmarshal(rec.Body.Bytes(), &env); err != nil {
t.Fatal(err)
}
if env.Code != CodeINTERNAL || env.RequestID != "req_test" {
t.Fatalf("%+v", env)
}
if strings.Contains(rec.Body.String(), "pq:") || strings.Contains(rec.Body.String(), "/home/") {
t.Fatalf("leaked: %s", rec.Body.String())
}
if env.Error != CodeINTERNAL {
t.Fatalf("legacy error=%s", env.Error)
}
}
func TestWriteKeepsStablePublicErrorCode(t *testing.T) {
rec := httptest.NewRecorder()
Write(rec, http.StatusForbidden, CodeAUTHZ, "ERR_FORBIDDEN", "req_2", nil)
var env Envelope
_ = json.Unmarshal(rec.Body.Bytes(), &env)
if env.Error != "ERR_FORBIDDEN" || env.Code != CodeAUTHZ {
t.Fatalf("%+v", env)
}
if strings.Contains(rec.Body.String(), "<script") {
t.Fatal("xss")
}
}
func TestLooksInternalXSS(t *testing.T) {
rec := httptest.NewRecorder()
Write(rec, http.StatusBadRequest, CodeVALIDATION, "<script>alert(1)</script>", "req_3", nil)
if strings.Contains(rec.Body.String(), "<script") {
t.Fatalf("%s", rec.Body.String())
}
}
func TestWriteLogsCodeWithoutInternalText(t *testing.T) {
var buf bytes.Buffer
prev := slog.Default()
slog.SetDefault(slog.New(slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelWarn})))
t.Cleanup(func() { slog.SetDefault(prev) })
req := httptest.NewRequest(http.MethodPost, "/api/v1/login?access_token=leak", nil)
req.Header.Set(HeaderRequestID, "req_log")
rec := httptest.NewRecorder()
WriteHTTP(rec, req, http.StatusInternalServerError, CodeINTERNAL, "pq: password /home/x", "req_log", "请求无法完成", nil)
line := buf.String()
if !strings.Contains(line, `"msg":"http_error"`) && !strings.Contains(line, "http_error") {
t.Fatalf("missing log: %s", line)
}
if !strings.Contains(line, "req_log") || !strings.Contains(line, CodeINTERNAL) {
t.Fatalf("log=%s", line)
}
if !strings.Contains(line, `"/api/v1/login"`) {
t.Fatalf("path=%s", line)
}
low := strings.ToLower(line)
for _, leak := range []string{"password", "pq:", "/home/", "access_token"} {
if strings.Contains(low, leak) {
t.Fatalf("leaked %s in %s", leak, line)
}
}
if rec.Header().Get(HeaderErrorCode) != CodeINTERNAL {
t.Fatalf("header=%s", rec.Header().Get(HeaderErrorCode))
}
}
func TestCodeFromStatus(t *testing.T) {
if CodeFromStatus(http.StatusUnauthorized) != CodeAUTHZ {
t.Fatal("401")
}
if CodeFromStatus(http.StatusConflict) != CodeVERSION_CONFLICT {
t.Fatal("409")
}
}