First Commit
ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
web / web (push) Waiting to run
docs / links (push) Canceled after 0s
detections / detections (push) Canceled after 0s

This commit is contained in:
dela
2026-10-09 08:38:16 +08:00
commit 0335d572de
756 changed files with 201663 additions and 0 deletions
+94
View File
@@ -0,0 +1,94 @@
package notify
import (
"net/url"
"testing"
"time"
)
// 签名基准值由 OpenSSL 独立算出,不是用本包自己的实现生成的——
// 否则只能证明「代码没变」,证明不了「算法对」。
//
// TS=1700000000000, SECRET=SECtest123
// 钉钉: printf '%s\n%s' "$TS" "$SECRET" | openssl dgst -sha256 -hmac "$SECRET" -binary | openssl base64 -A
// -> w3RMHXzixTMdzr8OHJUmVLS4IoPJVdu+Ut1LE48MePE=
// 飞书: printf '' | openssl dgst -sha256 -hmac "$(printf '%s\n%s' "$TS" "$SECRET")" -binary | openssl base64 -A
// -> Hd4xFWQU6R6ad4nzy4ETIznzlqebqH7xcTFVmONTudo=
const (
signTestTSMillis = int64(1700000000000)
signTestSecret = "SECtest123"
dingTalkExpected = "w3RMHXzixTMdzr8OHJUmVLS4IoPJVdu+Ut1LE48MePE="
feishuExpected = "Hd4xFWQU6R6ad4nzy4ETIznzlqebqH7xcTFVmONTudo="
)
func TestDingTalkSignMatchesReference(t *testing.T) {
got, err := dingTalkSignedURL("https://oapi.dingtalk.com/robot/send?access_token=tok", signTestSecret, time.UnixMilli(signTestTSMillis))
if err != nil {
t.Fatalf("签名失败: %v", err)
}
u, err := url.Parse(got)
if err != nil {
t.Fatalf("产出地址不可解析: %v", err)
}
q := u.Query()
if q.Get("sign") != dingTalkExpected {
t.Errorf("签名不符\n期望 %s\n得到 %s", dingTalkExpected, q.Get("sign"))
}
if q.Get("timestamp") != "1700000000000" {
t.Errorf("时间戳应为毫秒且原样带上,得到 %q", q.Get("timestamp"))
}
// 原有 query 参数(access_token)不能被签名覆盖掉。
if q.Get("access_token") != "tok" {
t.Errorf("原有 query 参数丢失,得到 %q", q.Get("access_token"))
}
}
func TestFeishuSignMatchesReference(t *testing.T) {
got := feishuSign("1700000000000", signTestSecret)
if got != feishuExpected {
t.Errorf("签名不符\n期望 %s\n得到 %s", feishuExpected, got)
}
}
// TestSignAlgorithmsDiffer 锁住两家的算法差异。它们刚好互为对方的参数顺序
// (钉钉 key=secret,飞书 key=待签串),照另一家抄必然校验失败,
// 这条用例确保将来重构不会把两者合并成同一个函数。
func TestSignAlgorithmsDiffer(t *testing.T) {
ts := "1700000000000"
dingURL, err := dingTalkSignedURL("https://example.com/hook", signTestSecret, time.UnixMilli(signTestTSMillis))
if err != nil {
t.Fatal(err)
}
dq, _ := url.Parse(dingURL)
if dq.Query().Get("sign") == feishuSign(ts, signTestSecret) {
t.Fatal("钉钉与飞书签名相同,说明其中一家的算法实现错了")
}
}
func TestDingTalkNoSecretLeavesURLUntouched(t *testing.T) {
// 未开启加签的机器人:不得凭空添加 timestamp/sign 参数。
const hook = "https://oapi.dingtalk.com/robot/send?access_token=tok"
got, err := dingTalkSignedURL(hook, "", time.UnixMilli(signTestTSMillis))
if err != nil {
t.Fatal(err)
}
if got != hook {
t.Fatalf("未配置 secret 时地址不应改动,得到 %q", got)
}
}
func TestValidateHTTPURL(t *testing.T) {
ok := []string{"https://example.com/hook", "http://10.0.0.1:8080/x?y=1"}
for _, s := range ok {
if err := validateHTTPURL(s); err != nil {
t.Errorf("%q 应被接受: %v", s, err)
}
}
// file:// 之类不应放行——http.Client 对它们的处理超出预期范围。
bad := []string{"", "file:///etc/passwd", "ftp://example.com", "https://", "gopher://x"}
for _, s := range bad {
if err := validateHTTPURL(s); err == nil {
t.Errorf("%q 应被拒绝", s)
}
}
}