package mail import ( "bytes" "html/template" "strings" "testing" "github.com/tamakyi/TamaBox/templates" ) // TestSecurityAlertTemplateRenders 保证 security-alert.html 模板可解析, // 且新增的「网络归属 / 代理网络提示」字段在参数齐全时正确渲染、 // proxy_hint 为空时不渲染提示块。 func TestSecurityAlertTemplateRenders(t *testing.T) { tmpl, err := template.ParseFS(templates.FS, "mail/security-alert.html") if err != nil { t.Fatalf("parse template: %v", err) } base := map[string]string{ "ip": "203.0.113.7", "provider": "Cloudflare(CDN)", "paths": "/admin、/.env", "duration": "15", "time": "2026-09-08 12:00:00", "year": "2026", } // 1. 代理网络命中:应有归属行 + 代理提示 var withHint bytes.Buffer params := cloneParams(base) params["proxy_hint"] = "1" if err := tmpl.Execute(&withHint, params); err != nil { t.Fatalf("execute with proxy_hint: %v", err) } out := withHint.String() for _, want := range []string{"203.0.113.7", "网络归属", "Cloudflare(CDN)", "代理网络"} { if !bytes.Contains([]byte(out), []byte(want)) { t.Errorf("rendered mail missing %q", want) } } // 2. 普通网络:不应出现代理提示块 var noHint bytes.Buffer if err := tmpl.Execute(&noHint, cloneParams(base)); err != nil { t.Fatalf("execute without proxy_hint: %v", err) } if bytes.Contains(noHint.Bytes(), []byte("代理网络")) { t.Errorf("proxy hint rendered without proxy_hint param") } } func cloneParams(src map[string]string) map[string]string { dst := make(map[string]string, len(src)) for k, v := range src { dst[k] = v } return dst } // TestHoneypotAlertTemplateRenders 保证蜜罐告警模板可解析,并且: // - IPv6(ip ≠ ban_key)时额外渲染「封禁范围」,说明封的是整个 /64 前缀; // - IPv4(ip == ban_key)时不渲染那一行,避免同一串地址出现两次; // - 正文说明的是「写入取证文件」而不是早已过时的「已入库」(凭据不入库)。 func TestHoneypotAlertTemplateRenders(t *testing.T) { tmpl, err := template.ParseFS(templates.FS, "mail/honeypot-alert.html") if err != nil { t.Fatalf("parse template: %v", err) } base := map[string]string{ "ip": "2408:8207:1234:5678::abcd", "ban_key": "2408:8207:1234:5678::/64", "provider": "中国联通(住宅宽带)", "path": "/wp-login.php", "username": "admin", "password": "hunter2", "disposition": "已记录并自动临时封禁 1440 分钟", "time": "2026-10-23 10:30:00", "year": "2026", } var v6 bytes.Buffer if err := tmpl.Execute(&v6, base); err != nil { t.Fatalf("execute ipv6: %v", err) } out := v6.String() for _, want := range []string{ "2408:8207:1234:5678::abcd", "封禁范围", "2408:8207:1234:5678::/64", "取证文件", } { if !strings.Contains(out, want) { t.Errorf("IPv6 邮件缺少 %q", want) } } if strings.Contains(out, "已入库") { t.Error("邮件仍在说「已入库」:凭据已改为写入文件,不在数据库里") } // IPv4:精确地址与封禁键相同,不该重复展示「封禁范围」。 v4 := cloneParams(base) v4["ip"] = "203.0.113.7" v4["ban_key"] = "203.0.113.7" var buf4 bytes.Buffer if err := tmpl.Execute(&buf4, v4); err != nil { t.Fatalf("execute ipv4: %v", err) } if strings.Contains(buf4.String(), "封禁范围") { t.Error("IPv4 下 ip == ban_key,不应渲染「封禁范围」行") } if !strings.Contains(buf4.String(), "203.0.113.7") { t.Error("IPv4 邮件缺少来源 IP") } }