placeholders.test.ts 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import { describe, expect, it } from "vitest";
  2. import {
  3. buildTemplateVariables,
  4. getTemplatePlaceholders,
  5. } from "@/lib/webhook/templates/placeholders";
  6. import type { StructuredMessage } from "@/lib/webhook/types";
  7. describe("Webhook Template Placeholders", () => {
  8. it("getTemplatePlaceholders should return common placeholders by default", () => {
  9. const placeholders = getTemplatePlaceholders();
  10. const keys = placeholders.map((p) => p.key);
  11. expect(keys).toContain("{{timestamp}}");
  12. expect(keys).toContain("{{sections}}");
  13. // 仅 common:至少 5 个
  14. expect(placeholders.length).toBeGreaterThanOrEqual(5);
  15. });
  16. it("getTemplatePlaceholders should include type-specific placeholders", () => {
  17. const placeholders = getTemplatePlaceholders("cost_alert");
  18. const keys = placeholders.map((p) => p.key);
  19. expect(keys).toContain("{{timestamp}}");
  20. expect(keys).toContain("{{usage_percent}}");
  21. });
  22. it("buildTemplateVariables should build common and circuit_breaker variables", () => {
  23. const message: StructuredMessage = {
  24. header: { title: "标题", level: "error" },
  25. sections: [
  26. {
  27. title: "信息",
  28. content: [
  29. { type: "text", value: "普通文本" },
  30. { type: "quote", value: "引用内容" },
  31. {
  32. type: "list",
  33. style: "ordered",
  34. items: [{ primary: "用户A", secondary: "消费 10" }],
  35. },
  36. ],
  37. },
  38. ],
  39. footer: [{ content: [{ type: "text", value: "footer" }] }],
  40. timestamp: new Date("2025-01-02T12:00:00Z"),
  41. };
  42. const vars = buildTemplateVariables({
  43. message,
  44. notificationType: "circuit_breaker",
  45. data: {
  46. providerName: "OpenAI",
  47. providerId: 1,
  48. failureCount: 3,
  49. retryAt: "2025-01-02T13:00:00Z",
  50. lastError: "timeout",
  51. },
  52. });
  53. expect(vars["{{title}}"]).toBe("标题");
  54. expect(vars["{{level}}"]).toBe("error");
  55. expect(vars["{{timestamp}}"]).toBe("2025-01-02T12:00:00.000Z");
  56. expect(vars["{{provider_name}}"]).toBe("OpenAI");
  57. expect(vars["{{provider_id}}"]).toBe("1");
  58. expect(vars["{{failure_count}}"]).toBe("3");
  59. expect(vars["{{retry_at}}"]).toBe("2025-01-02T13:00:00Z");
  60. expect(vars["{{last_error}}"]).toBe("timeout");
  61. expect(vars["{{sections}}"]).toContain("信息");
  62. expect(vars["{{sections}}"]).toContain("普通文本");
  63. expect(vars["{{sections}}"]).toContain("> 引用内容");
  64. expect(vars["{{sections}}"]).toContain("1. 用户A");
  65. expect(vars["{{sections}}"]).toContain("消费 10");
  66. expect(vars["{{sections}}"]).toContain("footer");
  67. });
  68. it("buildTemplateVariables should include endpoint variables when source is endpoint", () => {
  69. const message: StructuredMessage = {
  70. header: { title: "端点熔断告警", level: "error" },
  71. sections: [],
  72. timestamp: new Date("2025-01-02T12:00:00Z"),
  73. };
  74. const vars = buildTemplateVariables({
  75. message,
  76. notificationType: "circuit_breaker",
  77. data: {
  78. providerName: "OpenAI",
  79. providerId: 1,
  80. failureCount: 5,
  81. retryAt: "2025-01-02T13:00:00Z",
  82. lastError: "connection refused",
  83. incidentSource: "endpoint",
  84. endpointId: 42,
  85. endpointUrl: "https://api.openai.com/v1/chat/completions",
  86. },
  87. });
  88. expect(vars["{{incident_source}}"]).toBe("endpoint");
  89. expect(vars["{{endpoint_id}}"]).toBe("42");
  90. expect(vars["{{endpoint_url}}"]).toBe("https://api.openai.com/v1/chat/completions");
  91. });
  92. it("buildTemplateVariables should have empty endpoint variables when source is provider", () => {
  93. const message: StructuredMessage = {
  94. header: { title: "供应商熔断告警", level: "error" },
  95. sections: [],
  96. timestamp: new Date("2025-01-02T12:00:00Z"),
  97. };
  98. const vars = buildTemplateVariables({
  99. message,
  100. notificationType: "circuit_breaker",
  101. data: {
  102. providerName: "Anthropic",
  103. providerId: 2,
  104. failureCount: 3,
  105. retryAt: "2025-01-02T13:00:00Z",
  106. incidentSource: "provider",
  107. },
  108. });
  109. expect(vars["{{incident_source}}"]).toBe("provider");
  110. expect(vars["{{endpoint_id}}"]).toBe("");
  111. expect(vars["{{endpoint_url}}"]).toBe("");
  112. });
  113. it("buildTemplateVariables should handle daily_leaderboard entries JSON stringify errors", () => {
  114. // 构造循环引用,验证 safeJsonStringify 降级
  115. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  116. const circular: any[] = [];
  117. circular.push(circular);
  118. const message: StructuredMessage = {
  119. header: { title: "排行榜", level: "info" },
  120. sections: [],
  121. timestamp: new Date("2025-01-02T12:00:00Z"),
  122. };
  123. const vars = buildTemplateVariables({
  124. message,
  125. notificationType: "daily_leaderboard",
  126. data: {
  127. date: "2025-01-02",
  128. entries: circular,
  129. totalRequests: 10,
  130. totalCost: 1.23,
  131. },
  132. });
  133. expect(vars["{{date}}"]).toBe("2025-01-02");
  134. expect(vars["{{entries_json}}"]).toBe("[]");
  135. expect(vars["{{total_requests}}"]).toBe("10");
  136. expect(vars["{{total_cost}}"]).toBe("1.23");
  137. });
  138. it("buildTemplateVariables should compute usage percent for cost_alert", () => {
  139. const message: StructuredMessage = {
  140. header: { title: "成本预警", level: "warning" },
  141. sections: [],
  142. timestamp: new Date("2025-01-02T12:00:00Z"),
  143. };
  144. const vars = buildTemplateVariables({
  145. message,
  146. notificationType: "cost_alert",
  147. data: { targetType: "user", targetName: "张三", currentCost: 80, quotaLimit: 100 },
  148. });
  149. expect(vars["{{target_type}}"]).toBe("user");
  150. expect(vars["{{target_name}}"]).toBe("张三");
  151. expect(vars["{{usage_percent}}"]).toBe("80.0");
  152. });
  153. it("buildTemplateVariables should return empty usage percent when quota is invalid", () => {
  154. const message: StructuredMessage = {
  155. header: { title: "成本预警", level: "warning" },
  156. sections: [],
  157. timestamp: new Date("2025-01-02T12:00:00Z"),
  158. };
  159. const vars = buildTemplateVariables({
  160. message,
  161. notificationType: "cost_alert",
  162. data: { targetType: "user", targetName: "张三", currentCost: 80, quotaLimit: 0 },
  163. });
  164. expect(vars["{{usage_percent}}"]).toBe("");
  165. });
  166. });