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
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-bot/internal/archive"
|
||||
)
|
||||
|
||||
func sample() archive.RunArchive {
|
||||
return archive.RunArchive{Version: 1, Run: archive.RunSummary{RunID: "run-1", Operation: "sim_status_queue_full_sync", Status: "completed_with_failures", SuccessCount: 175893, FailedCount: 10504, CreatedAt: "2026-09-22T11:42:33Z", CompletedAt: "2026-09-22T12:32:01Z"}, Rounds: []archive.RoundStats{
|
||||
{Round: 1, EnqueuedCount: 186397, NextRoundCount: 9023, SuccessCount: 175870, FailedCount: 1504, StartedAt: "2026-09-22T11:42:34Z", CompletedAt: "2026-09-22T12:21:49Z"},
|
||||
{Round: 2, EnqueuedCount: 9023, NextRoundCount: 9000, SuccessCount: 23, FailedCount: 0, StartedAt: "2026-09-22T12:21:51Z", CompletedAt: "2026-09-22T12:26:55Z"},
|
||||
{Round: 3, EnqueuedCount: 9000, NextRoundCount: 0, SuccessCount: 0, FailedCount: 9000, StartedAt: "2026-09-22T12:26:56Z", CompletedAt: "2026-09-22T12:32:01Z"},
|
||||
}}
|
||||
}
|
||||
|
||||
func TestKindNamesEachSyncType(t *testing.T) {
|
||||
cases := []struct{ operation, want string }{
|
||||
{"previous_day_usage_full_sync", "日流量同步"},
|
||||
{"previous_month_usage_full_sync", "月历史同步"},
|
||||
{"current_month_usage_full_sync", "本月流量快照同步"},
|
||||
{"sim_status_queue_full_sync", "卡状态全量同步"},
|
||||
{"traffic_usage_full_sync", "流量全量同步"},
|
||||
{"usage_full_sync", "流量全量同步"},
|
||||
{"imei_full_sync", "imei_full_sync"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := Kind(c.operation); got != c.want {
|
||||
t.Errorf("Kind(%q) = %q, want %q", c.operation, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKindKeepsTrafficSyncTitlesDistinct(t *testing.T) {
|
||||
seen := map[string]string{}
|
||||
for _, operation := range []string{"previous_day_usage_full_sync", "previous_month_usage_full_sync", "current_month_usage_full_sync"} {
|
||||
title := Kind(operation)
|
||||
if other, exists := seen[title]; exists {
|
||||
t.Fatalf("%q and %q share title %q", other, operation, title)
|
||||
}
|
||||
seen[title] = operation
|
||||
}
|
||||
}
|
||||
|
||||
func TestCardTitleNamesTrafficSyncType(t *testing.T) {
|
||||
cases := []struct{ operation, want string }{
|
||||
{"previous_day_usage_full_sync", "生产 · 日流量同步"},
|
||||
{"previous_month_usage_full_sync", "生产 · 月历史同步"},
|
||||
{"current_month_usage_full_sync", "生产 · 本月流量快照同步"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
a := archive.RunArchive{Version: 1, Run: archive.RunSummary{RunID: "run-title", Operation: c.operation}}
|
||||
body, err := Card(a, "prod")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(body), `"content":"`+c.want+`"`) {
|
||||
t.Fatalf("card for %q missing title %q: %s", c.operation, c.want, body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextContainsRoundThreeFailure(t *testing.T) {
|
||||
text := Text(sample(), "preview")
|
||||
for _, want := range []string{"开始时间:2026-09-22 19:42:34", "结束时间:2026-09-22 20:32:01", "实际执行:3 轮", "第 3 轮", "终态失败 9,000", "5分5秒", "0.00%"} {
|
||||
if !strings.Contains(text, want) {
|
||||
t.Fatalf("text missing %q:\n%s", want, text)
|
||||
}
|
||||
}
|
||||
if strings.Contains(text, "2026-09-22 20:26:56") {
|
||||
t.Fatalf("text exposes per-round start time: %s", text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCardIsStructuredAndShowsFullRunID(t *testing.T) {
|
||||
body, err := Card(sample(), "preview")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
value := string(body)
|
||||
for _, want := range []string{
|
||||
`"msg_type":"interactive"`,
|
||||
`"content":"预演 · 卡状态全量同步"`,
|
||||
`"fields"`,
|
||||
`**开始时间**\n2026-09-22 19:42:34`,
|
||||
`**结束时间**\n2026-09-22 20:32:01`,
|
||||
`第 3 轮 · 9,000 张 · 5分5秒`,
|
||||
`进入下轮 0 · 终态失败 9,000`,
|
||||
`Run: run-1`,
|
||||
} {
|
||||
if !strings.Contains(value, want) {
|
||||
t.Fatalf("card missing %q: %s", want, body)
|
||||
}
|
||||
}
|
||||
if strings.Contains(value, "2026-09-22 20:26:56") {
|
||||
t.Fatalf("card exposes per-round start time: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCardShowsFullRunID(t *testing.T) {
|
||||
runID := "sim-status-20260922194233-c96b21ec7d3746ae9caef457e89faa41"
|
||||
card, err := Card(archive.RunArchive{Version: 1, Run: archive.RunSummary{RunID: runID}}, "preview")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(card), "Run: "+runID) {
|
||||
t.Fatalf("card truncated Run ID: %s", card)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user