Files

191 lines
4.7 KiB
Go

package logx
import (
"strings"
"sync"
"testing"
)
func TestMaskAPIKeys(t *testing.T) {
for _, input := range []string{"X-API-Key: fictional-secret", "api_key=fictional-secret", `{"apiKey":"fictional-secret"}`} {
if strings.Contains(Mask(input), "fictional-secret") {
t.Fatal("API Key escaped log masking")
}
}
}
// 这是本包最重要的测试:敏感内容绝不能出现在日志里。
// 每新增一条脱敏规则,都要在这里补一个用例。
func TestMaskHidesSecrets(t *testing.T) {
cases := []struct {
name string
in string
// leak 是绝对不能出现在结果里的片段
leak string
}{
{
name: "淘宝 MTOP token",
in: "cookie 里 _m_h5_tk=abc123def456_1699999999999 已读取",
leak: "abc123def456",
},
{
name: "淘宝 token enc",
in: "_m_h5_tk_enc=zzz999yyy888",
leak: "zzz999yyy888",
},
{
name: "淘宝 tb_token",
in: "_tb_token_=e3b0c44298fc; path=/",
leak: "e3b0c44298fc",
},
{
name: "Authorization 头",
in: "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.fake.token",
leak: "eyJhbGciOiJIUzI1NiJ9",
},
{
name: "Cookie 请求头",
in: "Cookie: a=1; b=2; secret=3",
leak: "secret=3",
},
{
name: "JSON 里的密码",
in: `{"account":"13500000000","password":"不该出现的密码"}`,
leak: "不该出现的密码",
},
{
name: "等号形式的密码",
in: "password=hunter2&next=1",
leak: "hunter2",
},
{
name: "中文冒号密码",
in: "登录失败,密码:明文密码值",
leak: "明文密码值",
},
{
name: "图片 base64",
in: "上传 data:image/jpeg;base64,AAAABBBBCCCCDDDD 完成",
leak: "AAAABBBBCCCCDDDD",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := Mask(tc.in)
if strings.Contains(got, tc.leak) {
t.Fatalf("脱敏后仍然泄漏了 %q\n原文:%s\n结果:%s", tc.leak, tc.in, got)
}
if !strings.Contains(got, "***") {
t.Fatalf("应当出现 *** 占位符,结果是:%s", got)
}
})
}
}
// 普通文本不应该被误伤。
func TestMaskKeepsNormalText(t *testing.T) {
in := "[3/6] 商品 40571188442 图搜返回 48 个同款,取前 20 个"
if got := Mask(in); got != in {
t.Fatalf("普通文本不应被改动\n原文:%s\n结果:%s", in, got)
}
}
// 写日志时也必须脱敏,不能只有直接调用 Mask 才生效。
func TestLoggerMasksOnWrite(t *testing.T) {
l := New(10)
l.Info("登录请求 password=真实密码")
entries := l.Entries()
if len(entries) != 1 {
t.Fatalf("应当有 1 条日志,实际 %d 条", len(entries))
}
if strings.Contains(entries[0].Message, "真实密码") {
t.Fatalf("日志里泄漏了密码:%s", entries[0].Message)
}
}
// 超过上限时要丢掉最旧的,防止长时间任务把内存吃光。
func TestLoggerRespectsLimit(t *testing.T) {
l := New(3)
for i := 0; i < 10; i++ {
l.Info("第 %d 条", i)
}
entries := l.Entries()
if len(entries) != 3 {
t.Fatalf("应当只保留 3 条,实际 %d 条", len(entries))
}
// 保留的应当是最后 3 条。
if !strings.Contains(entries[0].Message, "第 7 条") {
t.Fatalf("应当保留最新的日志,第一条却是:%s", entries[0].Message)
}
}
// 下载任务会有多个协程同时写日志,不能崩。
func TestLoggerIsConcurrentSafe(t *testing.T) {
l := New(1000)
var wg sync.WaitGroup
for i := 0; i < 20; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
for j := 0; j < 50; j++ {
l.Info("协程 %d 第 %d 条", n, j)
}
}(i)
}
wg.Wait()
if len(l.Entries()) != 1000 {
t.Fatalf("应当有 1000 条日志,实际 %d 条", len(l.Entries()))
}
}
func TestLoggerLevels(t *testing.T) {
l := New(10)
l.Info("信息")
l.Success("成功")
l.Warn("警告")
l.Error("失败")
want := []Level{LevelInfo, LevelSuccess, LevelWarn, LevelError}
entries := l.Entries()
for i, level := range want {
if entries[i].Level != level {
t.Fatalf("第 %d 条级别应当是 %s,实际 %s", i, level, entries[i].Level)
}
}
}
func TestLoggerClearAndText(t *testing.T) {
l := New(10)
l.Info("第一条")
l.Info("第二条")
text := l.Text()
if !strings.Contains(text, "第一条") || !strings.Contains(text, "第二条") {
t.Fatalf("导出文本应当包含全部日志,实际:%s", text)
}
l.Clear()
if len(l.Entries()) != 0 {
t.Fatalf("清空后不应还有日志")
}
}
// 日志写入后要能推给前端。
func TestLoggerHandler(t *testing.T) {
l := New(10)
var got []Entry
l.SetHandler(func(e Entry) { got = append(got, e) })
l.Info("一条日志")
if len(got) != 1 {
t.Fatalf("回调应当被调用 1 次,实际 %d 次", len(got))
}
if got[0].Message != "一条日志" {
t.Fatalf("回调收到的内容不对:%s", got[0].Message)
}
}