From bfc9d87b2499b45b9455262d8d4242b864ef1d4e Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Mon, 28 Sep 2026 09:50:24 +0800 Subject: [PATCH] =?UTF-8?q?test(push):=20=E8=A1=A5=20pi=20=E5=BB=BA?= =?UTF-8?q?=E8=AE=AE=E7=9A=84=E9=82=A3=E4=B8=80=E6=A0=BC=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20DailyLimit=3D1=20=E6=97=B6=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E5=90=8E=E4=BB=8D=E8=A6=81=E8=83=BD=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上一提交补的是**内部计数器**(dayCount == 0)。pi 在报告里建议的是 **用户看得见的行为**:DailyLimit=1,一次 accessToken 失败后第二次 仍然要能发出去。 两层都要钉的理由:计数器对而行为错是可能的 —— 那会让运维收到 「达到每日推送上限」这种**误导性文案**,真实原因却是上一次网络抖动。 变异验证:把 reserveDaily 挪回 accessToken 之前 ⇒ 两格同时红。 --- FAIL: TestHMSAccessTokenFailureDoesNotBurnQuota --- FAIL: TestHMSQuotaSurvivesTokenFailureWithLimitOne --- server/internal/push/push_test.go | 35 +++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/server/internal/push/push_test.go b/server/internal/push/push_test.go index 57f2678..940f8af 100644 --- a/server/internal/push/push_test.go +++ b/server/internal/push/push_test.go @@ -701,3 +701,38 @@ func TestHMSAccessTokenFailureDoesNotBurnQuota(t *testing.T) { t.Fatalf("成功这一次应恰好计 1 条,实际 %d", used) } } + +// TestHMSQuotaSurvivesTokenFailureWithLimitOne 用 pi 建议的形状再钉一遍: +// `DailyLimit=1` 时,一次 accessToken 失败之后**第二次仍然要能发出去**。 +// +// 为什么单独一格而不是并进上面那格:上面断言的是内部计数器(dayCount), +// 这格断言的是**用户看得见的行为** —— 失败后收到的下一个错误不能是 +// 「达到每日推送上限」这种误导性文案(它会让运维以为配额用完了, +// 而真实原因是上一次网络抖动)。计数器对、行为错是可能的, +// 所以两层都要钉。 +func TestHMSQuotaSurvivesTokenFailureWithLimitOne(t *testing.T) { + s := newHMSStub(t) + h := s.hms() + h.DailyLimit = 1 + + s.mu.Lock() + s.failToken = true + s.mu.Unlock() + err1 := h.Send(context.Background(), []string{"tok-1"}, NewMail{MailID: "m1"}) + if err1 == nil { + t.Fatal("accessToken 失败时必须返回错误") + } + + s.mu.Lock() + s.failToken = false + s.mu.Unlock() + err2 := h.Send(context.Background(), []string{"tok-2"}, NewMail{MailID: "m2"}) + + if err2 != nil { + if strings.Contains(err2.Error(), "上限") { + t.Fatalf("★ 第二次被当成配额用尽拒绝(%v)—— 那次失败烧掉了当日配额,"+ + "而用户看到的文案会指向错误的原因", err2) + } + t.Fatalf("恢复正常后第二次必须发出去,实际: %v", err2) + } +}