temp_unsched_test.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. //go:build unit
  2. package service
  3. import (
  4. "testing"
  5. "time"
  6. "github.com/stretchr/testify/require"
  7. )
  8. // ============ 临时限流单元测试 ============
  9. // TestMatchTempUnschedKeyword 测试关键词匹配函数
  10. func TestMatchTempUnschedKeyword(t *testing.T) {
  11. tests := []struct {
  12. name string
  13. body string
  14. keywords []string
  15. want string
  16. }{
  17. {
  18. name: "match_first",
  19. body: "server is overloaded",
  20. keywords: []string{"overloaded", "capacity"},
  21. want: "overloaded",
  22. },
  23. {
  24. name: "match_second",
  25. body: "no capacity available",
  26. keywords: []string{"overloaded", "capacity"},
  27. want: "capacity",
  28. },
  29. {
  30. name: "no_match",
  31. body: "internal error",
  32. keywords: []string{"overloaded", "capacity"},
  33. want: "",
  34. },
  35. {
  36. name: "empty_body",
  37. body: "",
  38. keywords: []string{"overloaded"},
  39. want: "",
  40. },
  41. {
  42. name: "empty_keywords",
  43. body: "server is overloaded",
  44. keywords: []string{},
  45. want: "",
  46. },
  47. {
  48. name: "whitespace_keyword",
  49. body: "server is overloaded",
  50. keywords: []string{" ", "overloaded"},
  51. want: "overloaded",
  52. },
  53. {
  54. // matchTempUnschedKeyword 期望 body 已经是小写的
  55. // 所以要测试大小写不敏感匹配,需要传入小写的 body
  56. name: "case_insensitive_body_lowered",
  57. body: "server is overloaded", // body 已经是小写
  58. keywords: []string{"OVERLOADED"}, // keyword 会被转为小写比较
  59. want: "OVERLOADED",
  60. },
  61. }
  62. for _, tt := range tests {
  63. t.Run(tt.name, func(t *testing.T) {
  64. got := matchTempUnschedKeyword(tt.body, tt.keywords)
  65. require.Equal(t, tt.want, got)
  66. })
  67. }
  68. }
  69. // TestAccountIsSchedulable_TempUnschedulable 测试临时限流账号不可调度
  70. func TestAccountIsSchedulable_TempUnschedulable(t *testing.T) {
  71. future := time.Now().Add(10 * time.Minute)
  72. past := time.Now().Add(-10 * time.Minute)
  73. tests := []struct {
  74. name string
  75. account *Account
  76. want bool
  77. }{
  78. {
  79. name: "temp_unschedulable_active",
  80. account: &Account{
  81. Status: StatusActive,
  82. Schedulable: true,
  83. TempUnschedulableUntil: &future,
  84. },
  85. want: false,
  86. },
  87. {
  88. name: "temp_unschedulable_expired",
  89. account: &Account{
  90. Status: StatusActive,
  91. Schedulable: true,
  92. TempUnschedulableUntil: &past,
  93. },
  94. want: true,
  95. },
  96. {
  97. name: "no_temp_unschedulable",
  98. account: &Account{
  99. Status: StatusActive,
  100. Schedulable: true,
  101. TempUnschedulableUntil: nil,
  102. },
  103. want: true,
  104. },
  105. {
  106. name: "temp_unschedulable_with_rate_limit",
  107. account: &Account{
  108. Status: StatusActive,
  109. Schedulable: true,
  110. TempUnschedulableUntil: &future,
  111. RateLimitResetAt: &past, // 过期的限流不影响
  112. },
  113. want: false, // 临时限流生效
  114. },
  115. }
  116. for _, tt := range tests {
  117. t.Run(tt.name, func(t *testing.T) {
  118. got := tt.account.IsSchedulable()
  119. require.Equal(t, tt.want, got)
  120. })
  121. }
  122. }
  123. // TestAccount_IsTempUnschedulableEnabled 测试临时限流开关
  124. func TestAccount_IsTempUnschedulableEnabled(t *testing.T) {
  125. tests := []struct {
  126. name string
  127. account *Account
  128. want bool
  129. }{
  130. {
  131. name: "enabled",
  132. account: &Account{
  133. Credentials: map[string]any{
  134. "temp_unschedulable_enabled": true,
  135. },
  136. },
  137. want: true,
  138. },
  139. {
  140. name: "disabled",
  141. account: &Account{
  142. Credentials: map[string]any{
  143. "temp_unschedulable_enabled": false,
  144. },
  145. },
  146. want: false,
  147. },
  148. {
  149. name: "not_set",
  150. account: &Account{
  151. Credentials: map[string]any{},
  152. },
  153. want: false,
  154. },
  155. {
  156. name: "nil_credentials",
  157. account: &Account{},
  158. want: false,
  159. },
  160. }
  161. for _, tt := range tests {
  162. t.Run(tt.name, func(t *testing.T) {
  163. got := tt.account.IsTempUnschedulableEnabled()
  164. require.Equal(t, tt.want, got)
  165. })
  166. }
  167. }
  168. // TestAccount_GetTempUnschedulableRules 测试获取临时限流规则
  169. func TestAccount_GetTempUnschedulableRules(t *testing.T) {
  170. tests := []struct {
  171. name string
  172. account *Account
  173. wantCount int
  174. }{
  175. {
  176. name: "has_rules",
  177. account: &Account{
  178. Credentials: map[string]any{
  179. "temp_unschedulable_rules": []any{
  180. map[string]any{
  181. "error_code": float64(503),
  182. "keywords": []any{"overloaded"},
  183. "duration_minutes": float64(5),
  184. },
  185. map[string]any{
  186. "error_code": float64(500),
  187. "keywords": []any{"internal"},
  188. "duration_minutes": float64(10),
  189. },
  190. },
  191. },
  192. },
  193. wantCount: 2,
  194. },
  195. {
  196. name: "empty_rules",
  197. account: &Account{
  198. Credentials: map[string]any{
  199. "temp_unschedulable_rules": []any{},
  200. },
  201. },
  202. wantCount: 0,
  203. },
  204. {
  205. name: "no_rules",
  206. account: &Account{
  207. Credentials: map[string]any{},
  208. },
  209. wantCount: 0,
  210. },
  211. {
  212. name: "nil_credentials",
  213. account: &Account{},
  214. wantCount: 0,
  215. },
  216. }
  217. for _, tt := range tests {
  218. t.Run(tt.name, func(t *testing.T) {
  219. rules := tt.account.GetTempUnschedulableRules()
  220. require.Len(t, rules, tt.wantCount)
  221. })
  222. }
  223. }
  224. // TestTempUnschedulableRule_Parse 测试规则解析
  225. func TestTempUnschedulableRule_Parse(t *testing.T) {
  226. account := &Account{
  227. Credentials: map[string]any{
  228. "temp_unschedulable_rules": []any{
  229. map[string]any{
  230. "error_code": float64(503),
  231. "keywords": []any{"overloaded", "capacity"},
  232. "duration_minutes": float64(5),
  233. },
  234. },
  235. },
  236. }
  237. rules := account.GetTempUnschedulableRules()
  238. require.Len(t, rules, 1)
  239. rule := rules[0]
  240. require.Equal(t, 503, rule.ErrorCode)
  241. require.Equal(t, []string{"overloaded", "capacity"}, rule.Keywords)
  242. require.Equal(t, 5, rule.DurationMinutes)
  243. }
  244. // TestTruncateTempUnschedMessage 测试消息截断
  245. func TestTruncateTempUnschedMessage(t *testing.T) {
  246. tests := []struct {
  247. name string
  248. body []byte
  249. maxBytes int
  250. want string
  251. }{
  252. {
  253. name: "short_message",
  254. body: []byte("short"),
  255. maxBytes: 100,
  256. want: "short",
  257. },
  258. {
  259. // 截断后会 TrimSpace,所以末尾的空格会被移除
  260. name: "truncate_long_message",
  261. body: []byte("this is a very long message that needs to be truncated"),
  262. maxBytes: 20,
  263. want: "this is a very long", // 截断后 TrimSpace
  264. },
  265. {
  266. name: "empty_body",
  267. body: []byte{},
  268. maxBytes: 100,
  269. want: "",
  270. },
  271. {
  272. name: "zero_max_bytes",
  273. body: []byte("test"),
  274. maxBytes: 0,
  275. want: "",
  276. },
  277. {
  278. name: "whitespace_trimmed",
  279. body: []byte(" test "),
  280. maxBytes: 100,
  281. want: "test",
  282. },
  283. }
  284. for _, tt := range tests {
  285. t.Run(tt.name, func(t *testing.T) {
  286. got := truncateTempUnschedMessage(tt.body, tt.maxBytes)
  287. require.Equal(t, tt.want, got)
  288. })
  289. }
  290. }
  291. // TestTempUnschedState 测试临时限流状态结构
  292. func TestTempUnschedState(t *testing.T) {
  293. now := time.Now()
  294. until := now.Add(5 * time.Minute)
  295. state := &TempUnschedState{
  296. UntilUnix: until.Unix(),
  297. TriggeredAtUnix: now.Unix(),
  298. StatusCode: 503,
  299. MatchedKeyword: "overloaded",
  300. RuleIndex: 0,
  301. ErrorMessage: "Server is overloaded",
  302. }
  303. require.Equal(t, 503, state.StatusCode)
  304. require.Equal(t, "overloaded", state.MatchedKeyword)
  305. require.Equal(t, 0, state.RuleIndex)
  306. // 验证时间戳
  307. require.Equal(t, until.Unix(), state.UntilUnix)
  308. require.Equal(t, now.Unix(), state.TriggeredAtUnix)
  309. }
  310. // TestAccount_TempUnschedulableUntil 测试临时限流时间字段
  311. func TestAccount_TempUnschedulableUntil(t *testing.T) {
  312. future := time.Now().Add(10 * time.Minute)
  313. past := time.Now().Add(-10 * time.Minute)
  314. tests := []struct {
  315. name string
  316. account *Account
  317. schedulable bool
  318. }{
  319. {
  320. name: "active_temp_unsched_not_schedulable",
  321. account: &Account{
  322. Status: StatusActive,
  323. Schedulable: true,
  324. TempUnschedulableUntil: &future,
  325. },
  326. schedulable: false,
  327. },
  328. {
  329. name: "expired_temp_unsched_is_schedulable",
  330. account: &Account{
  331. Status: StatusActive,
  332. Schedulable: true,
  333. TempUnschedulableUntil: &past,
  334. },
  335. schedulable: true,
  336. },
  337. {
  338. name: "nil_temp_unsched_is_schedulable",
  339. account: &Account{
  340. Status: StatusActive,
  341. Schedulable: true,
  342. TempUnschedulableUntil: nil,
  343. },
  344. schedulable: true,
  345. },
  346. }
  347. for _, tt := range tests {
  348. t.Run(tt.name, func(t *testing.T) {
  349. got := tt.account.IsSchedulable()
  350. require.Equal(t, tt.schedulable, got)
  351. })
  352. }
  353. }