guard-pipeline-warmup.test.ts 7.5 KB

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