balance_notify_email_body_test.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. //go:build unit
  2. package service
  3. import (
  4. "strings"
  5. "testing"
  6. "github.com/stretchr/testify/require"
  7. )
  8. // These tests guard against fmt.Sprintf arg-count mismatches in the email
  9. // templates. A mismatch would produce "%!(EXTRA ...)" or "%!v(MISSING)" in
  10. // the output, which these assertions will catch.
  11. // ---------- buildBalanceLowEmailBody ----------
  12. func TestBuildBalanceLowEmailBody_ContainsRequiredFields(t *testing.T) {
  13. s := &BalanceNotifyService{}
  14. body := s.buildBalanceLowEmailBody("Alice", 3.14, 10.0, "MySite", "")
  15. // All substituted values should appear in the output.
  16. require.Contains(t, body, "MySite")
  17. require.Contains(t, body, "Alice")
  18. require.Contains(t, body, "$3.14")
  19. require.Contains(t, body, "$10.00")
  20. // No fmt.Sprintf format error markers.
  21. require.NotContains(t, body, "%!")
  22. require.NotContains(t, body, "MISSING")
  23. require.NotContains(t, body, "EXTRA")
  24. }
  25. func TestBuildBalanceLowEmailBody_WithRechargeURL(t *testing.T) {
  26. s := &BalanceNotifyService{}
  27. body := s.buildBalanceLowEmailBody("Bob", 5.0, 20.0, "Site", "https://example.com/pay")
  28. // The recharge anchor element should appear with the URL.
  29. require.Contains(t, body, `href="https://example.com/pay"`)
  30. require.Contains(t, body, "立即充值")
  31. require.NotContains(t, body, "%!")
  32. }
  33. func TestBuildBalanceLowEmailBody_RechargeURLEscaped(t *testing.T) {
  34. s := &BalanceNotifyService{}
  35. // Try a URL with characters that need HTML escaping.
  36. body := s.buildBalanceLowEmailBody("u", 1.0, 5.0, "Site", `https://example.com/?a=1&b=<script>`)
  37. // `&` and `<` should be escaped in the href.
  38. require.Contains(t, body, "&amp;")
  39. require.Contains(t, body, "&lt;script&gt;")
  40. require.NotContains(t, body, "<script>")
  41. }
  42. func TestBuildBalanceLowEmailBody_NoRechargeURLOmitsButton(t *testing.T) {
  43. s := &BalanceNotifyService{}
  44. body := s.buildBalanceLowEmailBody("u", 1.0, 5.0, "Site", "")
  45. // The anchor element should not be rendered (style class may still appear).
  46. require.NotContains(t, body, `<a href`)
  47. require.NotContains(t, body, "立即充值")
  48. }
  49. // ---------- buildQuotaAlertEmailBody ----------
  50. func TestBuildQuotaAlertEmailBody_AllFieldsPresent(t *testing.T) {
  51. s := &BalanceNotifyService{}
  52. body := s.buildQuotaAlertEmailBody(
  53. 42, // accountID
  54. "acc-foo", // accountName
  55. "anthropic", // platform
  56. "日限额 / Daily", // dimLabel
  57. 750.50, // used
  58. 1000.0, // limit
  59. 249.50, // remaining
  60. "$249.50", // thresholdDisplay
  61. "MySite", // siteName
  62. )
  63. require.Contains(t, body, "MySite")
  64. require.Contains(t, body, "#42")
  65. require.Contains(t, body, "acc-foo")
  66. require.Contains(t, body, "anthropic")
  67. require.Contains(t, body, "Daily")
  68. require.Contains(t, body, "$750.50")
  69. require.Contains(t, body, "$1000.00")
  70. require.Contains(t, body, "$249.50")
  71. // No format error markers.
  72. require.NotContains(t, body, "%!")
  73. require.NotContains(t, body, "MISSING")
  74. require.NotContains(t, body, "EXTRA")
  75. }
  76. func TestBuildQuotaAlertEmailBody_UnlimitedDisplay(t *testing.T) {
  77. s := &BalanceNotifyService{}
  78. body := s.buildQuotaAlertEmailBody(
  79. 1, "n", "p", "dim",
  80. 100.0, 0.0, // limit=0 triggers unlimited branch
  81. 0.0, "30%", "Site",
  82. )
  83. require.Contains(t, body, "无限制")
  84. require.Contains(t, body, "Unlimited")
  85. }
  86. func TestBuildQuotaAlertEmailBody_PercentageThresholdDisplay(t *testing.T) {
  87. s := &BalanceNotifyService{}
  88. body := s.buildQuotaAlertEmailBody(
  89. 1, "n", "p", "dim",
  90. 700.0, 1000.0, 300.0,
  91. "30%", // percentage-formatted threshold
  92. "Site",
  93. )
  94. require.Contains(t, body, "30%")
  95. require.NotContains(t, body, "%!")
  96. }
  97. func TestBuildQuotaAlertEmailBody_RemainingClampedAtZero(t *testing.T) {
  98. // Even though caller is responsible for clamping, this test documents the
  99. // display behavior with remaining=0.
  100. s := &BalanceNotifyService{}
  101. body := s.buildQuotaAlertEmailBody(
  102. 1, "n", "p", "dim",
  103. 1500.0, 1000.0, 0.0, // used > limit (over-quota)
  104. "$100.00", "Site",
  105. )
  106. require.Contains(t, body, "$0.00")
  107. }
  108. // ---------- sanity checks on the CSS `%%` escape ----------
  109. func TestBuildBalanceLowEmailBody_NoCSSFormatError(t *testing.T) {
  110. s := &BalanceNotifyService{}
  111. body := s.buildBalanceLowEmailBody("u", 1.0, 5.0, "Site", "")
  112. // CSS `linear-gradient(135deg, #f59e0b 0%, #d97706 100%)` should appear with
  113. // literal percent signs (from the %% escape in the template).
  114. require.True(t,
  115. strings.Contains(body, "0%") && strings.Contains(body, "100%"),
  116. "CSS gradient percentages not rendered; got: %s", body)
  117. }
  118. func TestBuildQuotaAlertEmailBody_NoCSSFormatError(t *testing.T) {
  119. s := &BalanceNotifyService{}
  120. body := s.buildQuotaAlertEmailBody(1, "n", "p", "d", 0, 0, 0, "$0.00", "Site")
  121. require.True(t,
  122. strings.Contains(body, "0%") && strings.Contains(body, "100%"),
  123. "CSS gradient percentages not rendered; got: %s", body)
  124. }