Z ZPI 文档 v0.3.8
介绍页

源码

tests/desktop/model-selection.spec.ts

typescript191 行查看原文
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { ElectronApplication } from "@playwright/test";
import { chunk, deferred, done, fakeServer, send } from "../fake-server.ts";
import { runCleanup } from "../helpers/cleanup.ts";
import { launchDesktop } from "../helpers/desktop.ts";
import { expect, test } from "../helpers/test.ts";

test("a pending model selection preserves focus in an opened context panel", async () => {
  const dir = await mkdtemp(join(tmpdir(), "zpi-selection-focus-"));
  const server = await fakeServer((_, response) => done(response));
  let app: ElectronApplication | undefined;
  try {
    app = await launchDesktop({ dir, url: server.url });
    const page = await app.firstWindow();
    await app.evaluate(({ ipcMain }) => {
      const internal = ipcMain as unknown as {
        _invokeHandlers: Map<string, (event: unknown, method: string, args: unknown[]) => Promise<unknown>>;
      };
      const original = internal._invokeHandlers.get("zpi:call");
      if (!original) throw new Error("Missing IPC handler");
      let release!: () => void;
      const gate = new Promise<void>((resolve) => {
        release = resolve;
      });
      (globalThis as unknown as { releaseSelection: () => void }).releaseSelection = release;
      ipcMain.removeHandler("zpi:call");
      ipcMain.handle("zpi:call", async (event, method, args) => {
        if (method === "setSessionSelection") await gate;
        return original(event, method, args);
      });
    });
    const model = page.getByLabel("模型选择", { exact: true });
    await model.click();
    await page.getByRole("menuitem", { name: "fake", exact: true }).click();
    await expect(model).toBeDisabled();
    await page.keyboard.press("Escape");
    await expect(page.getByRole("menu")).toHaveCount(0);
    await page.getByLabel("添加上下文", { exact: true }).click();
    const panel = page.getByRole("listbox", { name: "添加上下文", exact: true });
    await expect(panel).toBeFocused();
    await app.evaluate(() => (globalThis as unknown as { releaseSelection: () => void }).releaseSelection());
    await expect(model).toBeEnabled();
    await page.evaluate(() => new Promise<void>((resolve) => requestAnimationFrame(() => resolve())));
    await expect(panel).toBeFocused();
    await panel.press("Escape");
    await expect(panel).toHaveCount(0);
    expect(server.requests).toHaveLength(0);
  } finally {
    await runCleanup([
      () => rm(dir, { recursive: true, force: true }),
      () => server.close(),
      () => app?.close(),
    ]);
  }
});

test("running Zhipu selection switches to DeepSeek for an immediate queued message and safe retry", async () => {
  const dir = await mkdtemp(join(tmpdir(), "zpi-switch-"));
  const project = join(dir, "project");
  await mkdir(project);
  await writeFile(join(project, "README.md"), "switch fixture");
  const waiting = deferred();
  const violations: unknown[] = [];
  let deepseekCalls = 0;
  const server = await fakeServer((body, response) => {
    const rows = body.messages as unknown as {
      role: string;
      content: string | null;
      tool_calls?: { id: string }[];
      tool_call_id?: string;
    }[];
    if (body.model === "glm-fixture") {
      if (!rows.some((row) => row.role === "tool")) {
        send(response, chunk({ reasoning_content: "查看文件" }));
        send(
          response,
          chunk({
            tool_calls: [
              {
                index: 0,
                id: "read-switch",
                type: "function",
                function: { name: "read", arguments: '{"path":"README.md"}' },
              },
            ],
          }),
        );
        done(response, "tool_calls");
      } else {
        response.write(": waiting\n\n");
        waiting.resolve();
      }
      return;
    }
    for (const row of rows)
      if (row.role === "assistant" && !row.content && !row.tool_calls?.length) violations.push(row);
    deepseekCalls++;
    if (rows.findLast((row) => row.role === "user")?.content === "切换后继续") {
      // A provider stream error leaves a durable empty assistant record; retry must exclude it too.
      done(response, "content_filter");
    } else {
      send(response, chunk({ content: "切换成功,历史继续" }));
      done(response);
    }
  });
  let app: ElectronApplication | undefined;
  try {
    app = await launchDesktop({ dir, project, url: server.url });
    const page = await app.firstWindow();
    await page.getByRole("button", { name: "添加项目", exact: true }).first().click();
    await page.evaluate(async (url) => {
      for (const [id, name, modelId] of [
        ["switch-zhipu", "智谱测试", "glm-fixture"],
        ["switch-deepseek", "DeepSeek测试", "deepseek-fixture"],
      ]) {
        const result = await window.zpi.saveProvider({
          id,
          name,
          baseUrl: url,
          apiKey: "isolated-key",
          models: [
            {
              id: modelId,
              name: modelId,
              input: ["text"],
              reasoning: true,
              contextWindow: 32768,
              maxTokens: 4096,
              compat: { supportsReasoningEffort: true, requiresReasoningContentOnAssistantMessages: true },
            },
          ],
        });
        if (!result.ok) throw new Error(result.error.message);
      }
    }, server.url);
    await page.reload();
    const choose = async (model: string) => {
      await page.getByLabel("模型选择", { exact: true }).click();
      await page.getByRole("menuitem", { name: model, exact: true }).hover();
      await page.getByRole("menuitem", { name: "关闭", exact: true }).click();
      await expect(page.getByLabel("模型选择", { exact: true })).toContainText(model);
      await expect(page.getByLabel("消息", { exact: true })).toBeFocused();
    };
    await choose("glm-fixture");
    const editor = page.getByLabel("消息", { exact: true });
    await editor.fill("查看文件");
    await editor.press("Enter");
    await waiting.promise;
    await choose("deepseek-fixture");
    await expect(page.getByTestId("run").last()).toHaveAttribute("data-status", "running");
    expect(deepseekCalls).toBe(0);
    await page.keyboard.type("切换后继续");
    await page.keyboard.press("Enter");
    await expect(page.getByTestId("queue-item")).toHaveCount(1);
    expect(deepseekCalls).toBe(0);
    await page.getByRole("button", { name: "立即", exact: true }).click();
    await expect(page.getByTestId("run").first()).toHaveAttribute("data-status", "aborted");
    await expect(page.getByTestId("run").last()).toHaveAttribute("data-status", "error");
    expect(deepseekCalls).toBe(1);
    await page.reload();
    await expect(page.getByTestId("run")).toHaveCount(2);
    await editor.fill("再次继续");
    await editor.press("Enter");
    await expect(page.getByTestId("run").last()).toHaveAttribute("data-status", "completed");
    await expect(page.locator(".answer").last()).toContainText("切换成功,历史继续");
    expect(violations).toEqual([]);
    expect(deepseekCalls).toBe(2);
    const last = server.requests.at(-1)?.messages as unknown as {
      role: string;
      tool_calls?: { id: string }[];
      tool_call_id?: string;
      content: string;
    }[];
    expect(last.find((row) => row.role === "assistant")?.tool_calls?.[0].id).toBe("read-switch");
    expect(last.find((row) => row.role === "tool")?.tool_call_id).toBe("read-switch");
    expect(last.filter((row) => row.role === "user").map((row) => row.content)).toEqual([
      "查看文件",
      "切换后继续",
      "再次继续",
    ]);
  } finally {
    await runCleanup([
      () => rm(dir, { recursive: true, force: true }),
      () => server.close(),
      () => app?.close(),
    ]);
  }
});