Browse Source

fix(test): fix sqlToString helper to handle drizzle column references

ding113 2 weeks ago
parent
commit
a6a6eb4a64
1 changed files with 196 additions and 0 deletions
  1. 196 0
      tests/unit/repository/message-origin-chain.test.ts

+ 196 - 0
tests/unit/repository/message-origin-chain.test.ts

@@ -0,0 +1,196 @@
+import { describe, expect, test, vi } from "vitest";
+import type { ProviderChainItem } from "@/types/message";
+
+function sqlToString(sqlObj: unknown): string {
+  const visited = new Set<unknown>();
+
+  const walk = (node: unknown): string => {
+    if (!node || visited.has(node)) return "";
+    visited.add(node);
+
+    if (typeof node === "string") return node;
+
+    if (typeof node === "object") {
+      const anyNode = node as any;
+      if (Array.isArray(anyNode)) {
+        return anyNode.map(walk).join("");
+      }
+
+      if (anyNode.name && typeof anyNode.name === "string") {
+        return anyNode.name;
+      }
+
+      if (anyNode.value) {
+        if (Array.isArray(anyNode.value)) {
+          return anyNode.value.map(String).join("");
+        }
+        return String(anyNode.value);
+      }
+
+      if (anyNode.queryChunks) {
+        return walk(anyNode.queryChunks);
+      }
+    }
+
+    return "";
+  };
+
+  return walk(sqlObj);
+}
+
+function createThenableQuery<T>(
+  result: T,
+  opts?: {
+    whereArgs?: unknown[];
+    orderByArgs?: unknown[];
+    limitArgs?: unknown[];
+  }
+) {
+  const query: any = Promise.resolve(result);
+
+  query.from = vi.fn(() => query);
+  query.where = vi.fn((arg: unknown) => {
+    opts?.whereArgs?.push(arg);
+    return query;
+  });
+  query.orderBy = vi.fn((...args: unknown[]) => {
+    opts?.orderByArgs?.push(args);
+    return query;
+  });
+  query.limit = vi.fn((arg: unknown) => {
+    opts?.limitArgs?.push(arg);
+    return query;
+  });
+
+  return query;
+}
+
+describe("repository/message findSessionOriginChain", () => {
+  test("happy path: 返回 session 首条非 warmup 的完整 providerChain", async () => {
+    vi.resetModules();
+
+    const whereArgs: unknown[] = [];
+    const orderByArgs: unknown[] = [];
+    const limitArgs: unknown[] = [];
+
+    const chain: ProviderChainItem[] = [
+      {
+        id: 101,
+        name: "provider-a",
+        reason: "initial_selection",
+        selectionMethod: "weighted_random",
+        attemptNumber: 1,
+      },
+    ];
+
+    const selectMock = vi.fn(() =>
+      createThenableQuery([{ providerChain: chain }], { whereArgs, orderByArgs, limitArgs })
+    );
+
+    vi.doMock("@/drizzle/db", () => ({
+      db: {
+        select: selectMock,
+        execute: vi.fn(async () => ({ count: 0 })),
+      },
+    }));
+
+    const { findSessionOriginChain } = await import("@/repository/message");
+    const result = await findSessionOriginChain("session-happy");
+
+    expect(result).toEqual(chain);
+    expect(whereArgs.length).toBeGreaterThan(0);
+
+    const whereSql = sqlToString(whereArgs[0]).toLowerCase();
+    expect(whereSql).toContain("warmup");
+    expect(whereSql).toContain("is not null");
+
+    expect(orderByArgs.length).toBeGreaterThan(0);
+    const orderSql = sqlToString(orderByArgs[0]).toLowerCase();
+    expect(orderSql).toContain("request_sequence");
+    expect(orderSql).toContain("asc");
+
+    expect(limitArgs).toEqual([1]);
+  });
+
+  test("warmup skip: 第一条为 warmup 时应返回后续首条非 warmup 的 chain", async () => {
+    vi.resetModules();
+
+    const chain: ProviderChainItem[] = [
+      {
+        id: 202,
+        name: "provider-b",
+        reason: "session_reuse",
+        selectionMethod: "session_reuse",
+        attemptNumber: 2,
+      },
+    ];
+
+    const selectMock = vi.fn(() => createThenableQuery([{ providerChain: chain }]));
+
+    vi.doMock("@/drizzle/db", () => ({
+      db: {
+        select: selectMock,
+        execute: vi.fn(async () => ({ count: 0 })),
+      },
+    }));
+
+    const { findSessionOriginChain } = await import("@/repository/message");
+    const result = await findSessionOriginChain("session-warmup-first");
+
+    expect(result).toEqual(chain);
+  });
+
+  test("no data: session 不存在时返回 null", async () => {
+    vi.resetModules();
+
+    const selectMock = vi.fn(() => createThenableQuery([]));
+
+    vi.doMock("@/drizzle/db", () => ({
+      db: {
+        select: selectMock,
+        execute: vi.fn(async () => ({ count: 0 })),
+      },
+    }));
+
+    const { findSessionOriginChain } = await import("@/repository/message");
+    const result = await findSessionOriginChain("session-not-found");
+
+    expect(result).toBeNull();
+  });
+
+  test("all warmup: 全部请求都被 warmup 拦截时返回 null", async () => {
+    vi.resetModules();
+
+    const selectMock = vi.fn(() => createThenableQuery([]));
+
+    vi.doMock("@/drizzle/db", () => ({
+      db: {
+        select: selectMock,
+        execute: vi.fn(async () => ({ count: 0 })),
+      },
+    }));
+
+    const { findSessionOriginChain } = await import("@/repository/message");
+    const result = await findSessionOriginChain("session-all-warmup");
+
+    expect(result).toBeNull();
+  });
+
+  test("null providerChain: 首条非 warmup 记录 providerChain 为空时返回 null", async () => {
+    vi.resetModules();
+
+    const selectMock = vi.fn(() => createThenableQuery([]));
+
+    vi.doMock("@/drizzle/db", () => ({
+      db: {
+        select: selectMock,
+        execute: vi.fn(async () => ({ count: 0 })),
+      },
+    }));
+
+    const { findSessionOriginChain } = await import("@/repository/message");
+    const result = await findSessionOriginChain("session-null-provider-chain");
+
+    expect(result).toBeNull();
+  });
+});