5732bf3384
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
87 lines
2.8 KiB
Go
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)
|
|
}
|
|
}
|