notification-settings.test.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. /**
  2. * 通知设置(Webhook Targets / Bindings)E2E 测试
  3. *
  4. * 覆盖范围:
  5. * - 创建/更新/删除推送目标
  6. * - 绑定通知类型与目标
  7. * - 验证创建目标后自动退出 legacy 模式(useLegacyMode=false)
  8. *
  9. * 前提:
  10. * - 开发服务器运行在 http://localhost:13500
  11. * - 已配置 ADMIN_TOKEN(或 TEST_ADMIN_TOKEN)
  12. */
  13. import { afterAll, beforeAll, describe, expect, test } from "vitest";
  14. import { loginAndGetAuthToken } from "./_helpers/auth";
  15. const API_BASE_URL = process.env.API_BASE_URL || "http://localhost:13500/api/actions";
  16. const ADMIN_KEY = process.env.TEST_ADMIN_TOKEN || process.env.ADMIN_TOKEN;
  17. const run = ADMIN_KEY ? describe : describe.skip;
  18. let authToken: string | undefined;
  19. async function callApi(module: string, action: string, body: Record<string, unknown> = {}) {
  20. if (!authToken) {
  21. throw new Error("E2E tests require ADMIN_TOKEN/TEST_ADMIN_TOKEN (used to login)");
  22. }
  23. const response = await fetch(`${API_BASE_URL}/${module}/${action}`, {
  24. method: "POST",
  25. headers: {
  26. "Content-Type": "application/json",
  27. Authorization: `Bearer ${authToken}`,
  28. Cookie: `auth-token=${authToken}`,
  29. },
  30. body: JSON.stringify(body),
  31. });
  32. const contentType = response.headers.get("content-type");
  33. if (contentType?.includes("application/json")) {
  34. const data = await response.json();
  35. return { response, data };
  36. }
  37. const text = await response.text();
  38. return { response, data: { ok: false, error: `非JSON响应: ${text}` } };
  39. }
  40. async function expectOk(module: string, action: string, body: Record<string, unknown> = {}) {
  41. const { response, data } = await callApi(module, action, body);
  42. expect(response.ok).toBe(true);
  43. expect(data.ok).toBe(true);
  44. return data.data;
  45. }
  46. const testState = {
  47. targetIds: [] as number[],
  48. };
  49. beforeAll(async () => {
  50. if (!ADMIN_KEY) return;
  51. authToken = await loginAndGetAuthToken(API_BASE_URL, ADMIN_KEY);
  52. });
  53. afterAll(async () => {
  54. if (!authToken) return;
  55. // 尽量清理测试数据(忽略失败)
  56. for (const id of testState.targetIds) {
  57. try {
  58. await callApi("webhook-targets", "deleteWebhookTargetAction", { id });
  59. } catch (_e) {
  60. // ignore
  61. }
  62. }
  63. });
  64. run("通知设置 - Webhook 目标与绑定(E2E)", () => {
  65. let targetId: number;
  66. test("1) 获取通知设置", async () => {
  67. const settings = await expectOk("notifications", "getNotificationSettingsAction");
  68. expect(settings).toBeDefined();
  69. expect(typeof settings.enabled).toBe("boolean");
  70. });
  71. test("2) 创建推送目标(custom)", async () => {
  72. const result = await expectOk("webhook-targets", "createWebhookTargetAction", {
  73. name: `E2E Webhook Target ${Date.now()}`,
  74. providerType: "custom",
  75. webhookUrl: "https://example.com/webhook",
  76. customTemplate: JSON.stringify({ text: "title={{title}}" }),
  77. customHeaders: { "X-Test": "1" },
  78. proxyUrl: null,
  79. proxyFallbackToDirect: false,
  80. isEnabled: true,
  81. });
  82. expect(result).toBeDefined();
  83. expect(result.id).toBeTypeOf("number");
  84. expect(result.providerType).toBe("custom");
  85. targetId = result.id;
  86. testState.targetIds.push(targetId);
  87. });
  88. test("3) 创建目标后应处于新模式(useLegacyMode=false)", async () => {
  89. const settings = await expectOk("notifications", "getNotificationSettingsAction");
  90. expect(settings.useLegacyMode).toBe(false);
  91. });
  92. test("4) 支持局部更新目标配置", async () => {
  93. const updated = await expectOk("webhook-targets", "updateWebhookTargetAction", {
  94. id: targetId,
  95. input: { isEnabled: false },
  96. });
  97. expect(updated.id).toBe(targetId);
  98. expect(updated.isEnabled).toBe(false);
  99. });
  100. test("5) 绑定 daily_leaderboard -> target", async () => {
  101. await expectOk("notification-bindings", "updateBindingsAction", {
  102. type: "daily_leaderboard",
  103. bindings: [{ targetId, isEnabled: true }],
  104. });
  105. const bindings = await expectOk("notification-bindings", "getBindingsForTypeAction", {
  106. type: "daily_leaderboard",
  107. });
  108. expect(Array.isArray(bindings)).toBe(true);
  109. expect(bindings.length).toBe(1);
  110. expect(bindings[0].targetId).toBe(targetId);
  111. expect(bindings[0].target.id).toBe(targetId);
  112. });
  113. test("6) 删除目标应使绑定不可见", async () => {
  114. await expectOk("webhook-targets", "deleteWebhookTargetAction", { id: targetId });
  115. // 从清理列表移除,避免重复删除
  116. testState.targetIds = testState.targetIds.filter((id) => id !== targetId);
  117. const bindings = await expectOk("notification-bindings", "getBindingsForTypeAction", {
  118. type: "daily_leaderboard",
  119. });
  120. expect(Array.isArray(bindings)).toBe(true);
  121. expect(bindings.length).toBe(0);
  122. });
  123. });