Files
iotlcm/internal/render/alert_test.go
T
MiFaZhan 5732bf3384 chore: 初始开源发布
iotlcm-bot:独立飞书应用机器人(读取 Worker 终态 Run JSON,渲染并发送全量同步结果卡片)。

- Go module:git.5iot.com/iotlcm/iotlcm-bot → git.5iot.com/iotlcm-public/iotlcm-bot
- 飞书凭据占位:appId/appSecret/chatId → ${FEISHU_APP_ID}/${FEISHU_APP_SECRET}/${FEISHU_CHAT_ID}
- 移除内部资产:CI 触发器
- Dockerfile 改用公共基础镜像(golang:1.25-alpine → scratch)
- README 重写(命令/配置/挂载契约/常见问题),去掉内部环境路径
- 补 Apache-2.0 LICENSE,LF 行尾 + .gitattributes
2026-10-22 16:14:24 +08:00

87 lines
2.8 KiB
Go

package render
import (
"encoding/json"
"strings"
"testing"
)
func TestAlertCardIsInteractiveWithRedHeader(t *testing.T) {
body, err := AlertCard(Alert{
Environment: "preview",
Title: "归档目录不可用",
Summary: "正在重启以重新挂载",
Reason: "宿主机上的归档目录已被删除,容器仍指向旧目录(挂载失效)。",
Path: "/data/full-sync",
})
if err != nil {
t.Fatal(err)
}
var decoded map[string]any
if err := json.Unmarshal(body, &decoded); err != nil {
t.Fatalf("alert card must be valid JSON: %v", err)
}
if decoded["msg_type"] != "interactive" {
t.Fatalf("alert must be an interactive card, got %v", decoded["msg_type"])
}
card := decoded["card"].(map[string]any)
header := card["header"].(map[string]any)
if header["template"] != "red" {
t.Fatalf("alert header should be red so it stands out, got %v", header["template"])
}
title := header["title"].(map[string]any)["content"].(string)
if !strings.Contains(title, "预演") || !strings.Contains(title, "归档目录不可用") {
t.Fatalf("unexpected title: %q", title)
}
}
func TestAlertCardShowsPathAndRepeatCount(t *testing.T) {
body, err := AlertCard(Alert{
Environment: "dev",
Title: "归档目录不可用",
Summary: "正在重启以重新挂载",
Reason: "目录不存在。",
Path: "/data/full-sync",
ExitCount: 3,
})
if err != nil {
t.Fatal(err)
}
text := string(body)
if !strings.Contains(text, "/data/full-sync") {
t.Fatal("card should show the path")
}
if !strings.Contains(text, "已重复 3 次") {
t.Fatal("card should warn about repeated exits")
}
}
// 单次故障时不该出现「已重复」这种话,避免误导。
func TestAlertCardOmitsRepeatNoticeOnFirstOccurrence(t *testing.T) {
body, err := AlertCard(Alert{Environment: "dev", Title: "归档目录不可用", ExitCount: 1})
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(body), "已重复") {
t.Fatal("first occurrence must not claim repeats")
}
}
func TestFriendlyReasonTranslatesKnownErrors(t *testing.T) {
cases := map[string]string{
"archive directory was deleted on the host (nlink=0); recreate this container": "宿主机上的归档目录已被删除",
"stat /data/x: no such file or directory": "目录不存在。",
"open /data/x: permission denied": "没有读取权限。",
}
for input, want := range cases {
if got := FriendlyReason(input); !strings.Contains(got, want) {
t.Fatalf("FriendlyReason(%q) = %q, want it to contain %q", input, got, want)
}
}
// 未知错误原样保留,便于排查
unknown := "some unexpected failure"
if got := FriendlyReason(unknown); got != unknown {
t.Fatalf("unknown reason must be preserved, got %q", got)
}
}