guard-pipeline-warmup.test.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import { describe, expect, test, vi } from "vitest";
  2. const callOrder: string[] = [];
  3. vi.mock("@/app/v1/_lib/proxy/auth-guard", () => ({
  4. ProxyAuthenticator: {
  5. ensure: async () => {
  6. callOrder.push("auth");
  7. return null;
  8. },
  9. },
  10. }));
  11. vi.mock("@/app/v1/_lib/proxy/client-guard", () => ({
  12. ProxyClientGuard: {
  13. ensure: async () => {
  14. callOrder.push("client");
  15. return null;
  16. },
  17. },
  18. }));
  19. vi.mock("@/app/v1/_lib/proxy/model-guard", () => ({
  20. ProxyModelGuard: {
  21. ensure: async () => {
  22. callOrder.push("model");
  23. return null;
  24. },
  25. },
  26. }));
  27. vi.mock("@/app/v1/_lib/proxy/version-guard", () => ({
  28. ProxyVersionGuard: {
  29. ensure: async () => {
  30. callOrder.push("version");
  31. return null;
  32. },
  33. },
  34. }));
  35. vi.mock("@/app/v1/_lib/proxy/session-guard", () => ({
  36. ProxySessionGuard: {
  37. ensure: async () => {
  38. callOrder.push("session");
  39. },
  40. },
  41. }));
  42. vi.mock("@/app/v1/_lib/proxy/warmup-guard", () => ({
  43. ProxyWarmupGuard: {
  44. ensure: async () => {
  45. callOrder.push("warmup");
  46. return new Response("ok", { status: 200 });
  47. },
  48. },
  49. }));
  50. vi.mock("@/app/v1/_lib/proxy/request-filter", () => ({
  51. ProxyRequestFilter: {
  52. ensure: async () => {
  53. callOrder.push("requestFilter");
  54. },
  55. },
  56. }));
  57. vi.mock("@/app/v1/_lib/proxy/sensitive-word-guard", () => ({
  58. ProxySensitiveWordGuard: {
  59. ensure: async () => {
  60. callOrder.push("sensitive");
  61. return null;
  62. },
  63. },
  64. }));
  65. vi.mock("@/app/v1/_lib/proxy/rate-limit-guard", () => ({
  66. ProxyRateLimitGuard: {
  67. ensure: async () => {
  68. callOrder.push("rateLimit");
  69. },
  70. },
  71. }));
  72. vi.mock("@/app/v1/_lib/proxy/provider-selector", () => ({
  73. ProxyProviderResolver: {
  74. ensure: async () => {
  75. callOrder.push("provider");
  76. return null;
  77. },
  78. },
  79. }));
  80. vi.mock("@/app/v1/_lib/proxy/provider-request-filter", () => ({
  81. ProxyProviderRequestFilter: {
  82. ensure: async () => {
  83. callOrder.push("providerRequestFilter");
  84. },
  85. },
  86. }));
  87. vi.mock("@/app/v1/_lib/proxy/message-service", () => ({
  88. ProxyMessageService: {
  89. ensureContext: async () => {
  90. callOrder.push("messageContext");
  91. },
  92. },
  93. }));
  94. describe("GuardPipeline:warmup 拦截点", () => {
  95. test("CHAT pipeline 必须包含 warmup,且位于 session 之后、requestFilter 之前", async () => {
  96. const { CHAT_PIPELINE } = await import("@/app/v1/_lib/proxy/guard-pipeline");
  97. const sensitiveIdx = CHAT_PIPELINE.steps.indexOf("sensitive");
  98. const sessionIdx = CHAT_PIPELINE.steps.indexOf("session");
  99. const warmupIdx = CHAT_PIPELINE.steps.indexOf("warmup");
  100. const requestFilterIdx = CHAT_PIPELINE.steps.indexOf("requestFilter");
  101. expect(sensitiveIdx).toBeGreaterThanOrEqual(0);
  102. expect(sessionIdx).toBeGreaterThanOrEqual(0);
  103. expect(sensitiveIdx).toBeLessThan(sessionIdx);
  104. expect(warmupIdx).toBe(sessionIdx + 1);
  105. expect(requestFilterIdx).toBeGreaterThan(warmupIdx);
  106. });
  107. test("warmup 抢答后应提前结束,不应触发 rateLimit/provider 等后续步骤", async () => {
  108. callOrder.length = 0;
  109. const { GuardPipelineBuilder, RequestType } = await import(
  110. "@/app/v1/_lib/proxy/guard-pipeline"
  111. );
  112. const pipeline = GuardPipelineBuilder.fromRequestType(RequestType.CHAT);
  113. const session = {
  114. isProbeRequest: () => {
  115. callOrder.push("probe");
  116. return false;
  117. },
  118. } as any;
  119. const res = await pipeline.run(session);
  120. expect(res).not.toBeNull();
  121. expect(res?.status).toBe(200);
  122. expect(callOrder).toEqual([
  123. "auth",
  124. "sensitive",
  125. "client",
  126. "model",
  127. "version",
  128. "probe",
  129. "session",
  130. "warmup",
  131. ]);
  132. expect(callOrder).not.toContain("rateLimit");
  133. expect(callOrder).not.toContain("provider");
  134. expect(callOrder).not.toContain("messageContext");
  135. });
  136. test("probe 请求应在 probe 步骤提前结束,不应触发 session/warmup 等后续步骤", async () => {
  137. callOrder.length = 0;
  138. const { GuardPipelineBuilder, RequestType } = await import(
  139. "@/app/v1/_lib/proxy/guard-pipeline"
  140. );
  141. const pipeline = GuardPipelineBuilder.fromRequestType(RequestType.CHAT);
  142. const session = {
  143. isProbeRequest: () => {
  144. callOrder.push("probe");
  145. return true;
  146. },
  147. } as any;
  148. const res = await pipeline.run(session);
  149. expect(res).not.toBeNull();
  150. expect(res?.status).toBe(200);
  151. expect(callOrder).toEqual(["auth", "sensitive", "client", "model", "version", "probe"]);
  152. expect(callOrder).not.toContain("session");
  153. expect(callOrder).not.toContain("warmup");
  154. expect(callOrder).not.toContain("rateLimit");
  155. expect(callOrder).not.toContain("provider");
  156. expect(callOrder).not.toContain("messageContext");
  157. });
  158. test("COUNT_TOKENS pipeline 应走最小链路(且覆盖 fromRequestType 分支)", async () => {
  159. callOrder.length = 0;
  160. const { GuardPipelineBuilder, RequestType } = await import(
  161. "@/app/v1/_lib/proxy/guard-pipeline"
  162. );
  163. const pipeline = GuardPipelineBuilder.fromRequestType(RequestType.COUNT_TOKENS);
  164. const session = {
  165. isProbeRequest: () => {
  166. callOrder.push("probe");
  167. return false;
  168. },
  169. } as any;
  170. const res = await pipeline.run(session);
  171. expect(res).toBeNull();
  172. expect(callOrder).toEqual([
  173. "auth",
  174. "client",
  175. "model",
  176. "version",
  177. "probe",
  178. "requestFilter",
  179. "provider",
  180. "providerRequestFilter",
  181. ]);
  182. expect(callOrder).not.toContain("session");
  183. expect(callOrder).not.toContain("warmup");
  184. expect(callOrder).not.toContain("sensitive");
  185. expect(callOrder).not.toContain("rateLimit");
  186. expect(callOrder).not.toContain("messageContext");
  187. });
  188. });