From 1ea42971a88bd12a2206f13e301db6dbc0389481 Mon Sep 17 00:00:00 2001 From: Zhouyinxi Date: Sat, 31 Oct 2026 22:38:09 +0800 Subject: [PATCH] fix: handle deleted refs in push notifications --- example-configs/templates/cn/push.json | 30 +++++++++ example-configs/templates/default/push.json | 30 +++++++++ internal/handler/handler_push.go | 9 +++ internal/handler/handler_test.go | 67 +++++++++++++++++++++ internal/template/template.go | 4 +- 5 files changed, 139 insertions(+), 1 deletion(-) diff --git a/example-configs/templates/cn/push.json b/example-configs/templates/cn/push.json index ae21c84..e5c67ec 100644 --- a/example-configs/templates/cn/push.json +++ b/example-configs/templates/cn/push.json @@ -85,6 +85,36 @@ ] } } + }, + { + "tags": [ + "push", + "deleted" + ], + "payload": { + "msg_type": "interactive", + "card": { + "config": { + "wide_screen_mode": true + }, + "header": { + "title": { + "tag": "plain_text", + "content": "🗑️ 删除:{{ref_type}} 在 {{repository.full_name}}" + }, + "template": "red" + }, + "elements": [ + { + "tag": "div", + "text": { + "tag": "lark_md", + "content": "**触发者:** {{sender_link_md | default(sender.login)}}\n**删除:** {{ref_type}} {{ref}}\n" + } + } + ] + } + } } ] } diff --git a/example-configs/templates/default/push.json b/example-configs/templates/default/push.json index 3f86d95..ebdf087 100644 --- a/example-configs/templates/default/push.json +++ b/example-configs/templates/default/push.json @@ -85,6 +85,36 @@ ] } } + }, + { + "tags": [ + "push", + "deleted" + ], + "payload": { + "msg_type": "interactive", + "card": { + "config": { + "wide_screen_mode": true + }, + "header": { + "title": { + "tag": "plain_text", + "content": "🗑️ Deleted: {{ref_type}} in {{repository.full_name}}" + }, + "template": "red" + }, + "elements": [ + { + "tag": "div", + "text": { + "tag": "lark_md", + "content": "**Actor:** {{sender_link_md | default(sender.login)}}\n**Deleted:** {{ref_type}} {{ref}}\n" + } + } + ] + } + } } ] } diff --git a/internal/handler/handler_push.go b/internal/handler/handler_push.go index 8907c10..dc42dad 100644 --- a/internal/handler/handler_push.go +++ b/internal/handler/handler_push.go @@ -68,6 +68,15 @@ func preparePushData(data map[string]any, payload map[string]any) { data["forced"] = payload["forced"] if ref, ok := payload["ref"].(string); ok { + if deleted, _ := payload["deleted"].(bool); deleted { + if strings.HasPrefix(ref, "refs/heads/") { + data["ref_type"] = "branch" + } else if strings.HasPrefix(ref, "refs/tags/") { + data["ref_type"] = "tag" + } else { + data["ref_type"] = "ref" + } + } branch := strings.TrimPrefix(ref, "refs/heads/") data["branch_name"] = branch if repo, ok := payload["repository"].(map[string]any); ok { diff --git a/internal/handler/handler_test.go b/internal/handler/handler_test.go index a4b42bd..2feb539 100644 --- a/internal/handler/handler_test.go +++ b/internal/handler/handler_test.go @@ -1,9 +1,11 @@ package handler import ( + "encoding/json" "net/http" "net/http/httptest" "os" + "path/filepath" "testing" "net/url" @@ -12,8 +14,73 @@ import ( "github.com/hnrobert/feishu-github-tracker/internal/config" "github.com/hnrobert/feishu-github-tracker/internal/logger" "github.com/hnrobert/feishu-github-tracker/internal/notifier" + "github.com/hnrobert/feishu-github-tracker/internal/template" ) +func TestPushTemplates(t *testing.T) { + cfg, err := config.Load(filepath.Join("..", "..", "example-configs")) + if err != nil { + t.Fatal(err) + } + h := New(cfg, notifier.New(config.FeishuBotsConfig{})) + tests := []struct { + name, ref, kind, title, titleCN string + deleted, forced bool + }{ + {"branch", "refs/heads/feature/test", "branch", "Deleted:", "删除:", true, false}, + {"tag", "refs/tags/v1/test", "tag", "Deleted:", "删除:", true, false}, + {"normal empty push", "refs/heads/main", "", "New Push", "新推送", false, false}, + {"force", "refs/heads/main", "", "New Push", "新推送", false, true}, + {"deleted and forced", "refs/heads/feature/test", "branch", "Deleted:", "删除:", true, true}, + } + for _, tt := range tests { + for _, locale := range []string{"default", "cn"} { + t.Run(tt.name+"/"+locale, func(t *testing.T) { + payload := map[string]any{ + "ref": tt.ref, "deleted": tt.deleted, "forced": tt.forced, "commits": []any{}, + "compare": "https://example.com/compare", + "repository": map[string]any{"full_name": "org/repo", "html_url": "https://github.com/org/repo"}, + "sender": map[string]any{"login": "alice", "html_url": "https://github.com/alice"}, + "pusher": map[string]any{"name": "alice"}, + } + data := h.prepareTemplateData("push", payload) + selected, err := template.SelectTemplate("push", template.DetermineTags("push", payload), cfg.GetTemplateConfig(locale)) + if err != nil { + t.Fatal(err) + } + card, err := template.FillTemplate(selected, data) + if err != nil { + t.Fatal(err) + } + encoded, err := json.Marshal(card) + if err != nil { + t.Fatal(err) + } + got := string(encoded) + title := tt.title + if locale == "cn" { + title = tt.titleCN + } + if !strings.Contains(got, title) { + t.Fatalf("card missing title %q: %s", title, got) + } + if tt.deleted { + for _, want := range []string{"alice", "org/repo", tt.kind, tt.ref} { + if !strings.Contains(got, want) { + t.Errorf("card missing %q: %s", want, got) + } + } + for _, unwanted := range []string{"commits", "提交", "compare", "/tree/", `"tag":"action"`} { + if strings.Contains(got, unwanted) { + t.Errorf("deletion card contains %q: %s", unwanted, got) + } + } + } + }) + } + } +} + func TestPrepareTemplateData_IncludesNestedObjects(t *testing.T) { // Minimal config and notifier stub cfg := &config.Config{} diff --git a/internal/template/template.go b/internal/template/template.go index 1102f44..f9b3873 100644 --- a/internal/template/template.go +++ b/internal/template/template.go @@ -373,7 +373,9 @@ func DetermineTags(eventType string, payload map[string]any) []string { switch eventType { case "push": - if forced, ok := payload["forced"].(bool); ok && forced { + if deleted, ok := payload["deleted"].(bool); ok && deleted { + tags = append(tags, "deleted") + } else if forced, ok := payload["forced"].(bool); ok && forced { tags = append(tags, "force") } else { tags = append(tags, "default")