快照段(飞书→SQLite 镜像→离职双确认推进)落地,不碰 LLDAP: - feishu 新增离职确认卡片:自定义机器人 webhook 发送 interactive 卡片, 确认按钮 url 跳程序自己的一次性端点;本地假 webhook 单测验证载荷与错误处理 - config.ConfirmURL/ResignConfirmRoute:确认端点路由与 URL 拼接(server/syncer 共用避免循环) - mapping.FlatDeptInfo:部门树拍平为拼音点分命名并计算 DN(纯函数), 供快照落库持久化 pinyin_name/dn,物化与 WebUI 复用 - SnapshotRunner.RunSnapshot:拉飞书→事务写镜像→离职生命周期 reconcile (机器确认→token+卡片→pending_confirm;节流防刷屏;重新可见撤销并作废 token) - 链路单测:快照落库、离职机器确认、发卡、状态机转移、节流、重新入职全部通过
134 lines
4.4 KiB
Go
134 lines
4.4 KiB
Go
package feishu
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func testCard() *ResignCard {
|
|
return &ResignCard{
|
|
DisplayName: "张三",
|
|
UID: "zhangsan",
|
|
Reason: "飞书通讯录中已不可见,经机器确认已离职。",
|
|
ConfirmURL: "https://confirm.example.com/api/resign/confirm?uid=zhangsan&token=abc123",
|
|
}
|
|
}
|
|
|
|
// TestBuildResignCardPayload 验证卡片载荷基本结构完整,且包含了姓名/uid/离职依据/确认链接。
|
|
func TestBuildResignCardPayload(t *testing.T) {
|
|
raw, err := BuildResignCardPayload(testCard())
|
|
if err != nil {
|
|
t.Fatalf("构造卡片载荷失败: %v", err)
|
|
}
|
|
|
|
var doc struct {
|
|
MsgType string `json:"msg_type"`
|
|
Card struct {
|
|
Header struct {
|
|
Title struct {
|
|
Content string `json:"content"`
|
|
} `json:"title"`
|
|
} `json:"header"`
|
|
Elements []map[string]any `json:"elements"`
|
|
} `json:"card"`
|
|
}
|
|
if err := json.Unmarshal(raw, &doc); err != nil {
|
|
t.Fatalf("卡片载荷不是合法 JSON: %v", err)
|
|
}
|
|
|
|
if doc.MsgType != "interactive" {
|
|
t.Errorf("msg_type = %q, 期望 interactive", doc.MsgType)
|
|
}
|
|
if doc.Card.Header.Title.Content != "离职确认" {
|
|
t.Errorf("卡片标题 = %q, 期望 离职确认", doc.Card.Header.Title.Content)
|
|
}
|
|
|
|
// 找到 div 里的正文与 action 里的确认按钮。
|
|
joined := string(raw)
|
|
for _, want := range []string{"张三", "zhangsan", "飞书通讯录中已不可见", "https://confirm.example.com"} {
|
|
if !strings.Contains(joined, want) {
|
|
t.Errorf("卡片载荷缺少关键内容 %q", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestBuildResignCardPayloadRejectsMissingConfirmURL 验证没有确认链接时不构造卡片。
|
|
// 卡片按钮是该流程的唯一确认入口,缺失链接等于拿掉半道闸,必须拒绝。
|
|
func TestBuildResignCardPayloadRejectsMissingConfirmURL(t *testing.T) {
|
|
c := testCard()
|
|
c.ConfirmURL = ""
|
|
if _, err := BuildResignCardPayload(c); err == nil {
|
|
t.Errorf("缺少确认链接时不应构造卡片,却成功了")
|
|
}
|
|
}
|
|
|
|
// TestSendResignCardSuccess 验证向本地假 webhook 发送成功并携带正确载荷。
|
|
func TestSendResignCardSuccess(t *testing.T) {
|
|
var received []byte
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
t.Errorf("请求方法 = %s, 期望 POST", r.Method)
|
|
}
|
|
if ct := r.Header.Get("Content-Type"); !strings.Contains(ct, "application/json") {
|
|
t.Errorf("Content-Type = %q", ct)
|
|
}
|
|
received, _ = io.ReadAll(r.Body)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"StatusCode":0,"StatusMessage":"success"}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
payload, _ := BuildResignCardPayload(testCard())
|
|
if err := SendResignCard(context.Background(), srv.URL, payload); err != nil {
|
|
t.Fatalf("发送失败: %v", err)
|
|
}
|
|
if len(received) == 0 {
|
|
t.Errorf("webhook 未收到请求体")
|
|
}
|
|
if string(received) != string(payload) {
|
|
t.Errorf("webhook 收到的载荷与构造的不一致")
|
|
}
|
|
}
|
|
|
|
// TestSendResignCardWebhookError 验证 webhook 返回错误码时上报失败。
|
|
func TestSendResignCardWebhookError(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = w.Write([]byte(`{"code":9499,"msg":"openapi signature error"}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
payload, _ := BuildResignCardPayload(testCard())
|
|
err := SendResignCard(context.Background(), srv.URL, payload)
|
|
if err == nil {
|
|
t.Fatalf("webhook 返回错误码应上报失败,却成功了")
|
|
}
|
|
if !strings.Contains(err.Error(), "9499") {
|
|
t.Errorf("错误信息应包含错误码,实际: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestSendResignCardRejectsEmptyURL 验证未配置 webhook 地址时报错。
|
|
func TestSendResignCardRejectsEmptyURL(t *testing.T) {
|
|
payload, _ := BuildResignCardPayload(testCard())
|
|
if err := SendResignCard(context.Background(), "", payload); err == nil {
|
|
t.Errorf("空 webhook 地址应报错,却成功了")
|
|
}
|
|
}
|
|
|
|
// TestSendResignCardRejectsNon2xx 验证 HTTP 非 200 时上报失败。
|
|
func TestSendResignCardRejectsNon2xx(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
payload, _ := BuildResignCardPayload(testCard())
|
|
if err := SendResignCard(context.Background(), srv.URL, payload); err == nil {
|
|
t.Errorf("HTTP 400 应上报失败,却成功了")
|
|
}
|
|
} |