401 lines
13 KiB
TypeScript
401 lines
13 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
|
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
import { render } from "@testing-library/react";
|
|
import { describe, expect, mock, test } from "bun:test";
|
|
import { createElement, useRef } from "react";
|
|
|
|
import {
|
|
useCreateModel,
|
|
useDeleteModel,
|
|
useTestModelConnection,
|
|
useUpdateModel,
|
|
} from "../../../src/web/hooks/use-models";
|
|
import {
|
|
useArchiveProject,
|
|
useCreateProject,
|
|
useDeleteProject,
|
|
useRestoreProject,
|
|
useUpdateProject,
|
|
} from "../../../src/web/hooks/use-projects";
|
|
import {
|
|
useCreateProvider,
|
|
useDeleteProvider,
|
|
useTestProviderConfig,
|
|
useUpdateProvider,
|
|
} from "../../../src/web/hooks/use-providers";
|
|
import { installFetchMock, jsonResponse } from "../test-utils";
|
|
|
|
const MODEL = {
|
|
autoAdapt: true,
|
|
capabilities: ["text"] as string[],
|
|
createdAt: "2024-01-01T00:00:00.000Z",
|
|
customApiKey: null,
|
|
customBaseUrl: null,
|
|
description: "测试模型",
|
|
id: "m1",
|
|
modelId: "gpt-4",
|
|
name: "测试模型",
|
|
providerId: "prov-1",
|
|
updatedAt: "2024-01-01T00:00:00.000Z",
|
|
};
|
|
|
|
const PROJECT = {
|
|
archivedAt: null,
|
|
createdAt: "2024-01-01T00:00:00.000Z",
|
|
description: "测试",
|
|
id: "p1",
|
|
name: "测试项目",
|
|
status: "active" as const,
|
|
updatedAt: "2024-01-01T00:00:00.000Z",
|
|
};
|
|
|
|
const PROVIDER = {
|
|
createdAt: "2024-01-01T00:00:00.000Z",
|
|
id: "prov-1",
|
|
name: "测试供应商",
|
|
type: "openai" as const,
|
|
updatedAt: "2024-01-01T00:00:00.000Z",
|
|
};
|
|
|
|
function getLogMessages(spy: ReturnType<typeof mock>) {
|
|
return spy.mock.calls.map((c) => c[0] as string).filter((s) => s.includes("[Alfred:INFO]"));
|
|
}
|
|
|
|
function makeQueryClient() {
|
|
return new QueryClient({
|
|
defaultOptions: { queries: { retry: false } },
|
|
});
|
|
}
|
|
|
|
function setupModelFetches(result: unknown) {
|
|
installFetchMock((call) => {
|
|
if (call.method === "DELETE") return new Response(null, { status: 204 });
|
|
if (call.url.includes("test")) return jsonResponse({ modelTestResponse: { message: "ok", ok: true } });
|
|
return jsonResponse({ model: result }, { status: 201 });
|
|
});
|
|
}
|
|
|
|
function setupProjectFetches(result: unknown) {
|
|
installFetchMock((call) => {
|
|
if (call.method === "DELETE") return new Response(null, { status: 204 });
|
|
if (call.url.includes("archive")) return jsonResponse({ project: result });
|
|
if (call.url.includes("restore")) return jsonResponse({ project: result });
|
|
return jsonResponse({ project: result }, { status: 201 });
|
|
});
|
|
}
|
|
|
|
function setupProviderFetches(result: unknown) {
|
|
installFetchMock((call) => {
|
|
if (call.method === "DELETE") return new Response(null, { status: 204 });
|
|
if (call.url.includes("test")) return jsonResponse({ providerTestResponse: { message: "ok", ok: true } });
|
|
return jsonResponse({ provider: result }, { status: 201 });
|
|
});
|
|
}
|
|
|
|
function spyConsoleLog() {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const spy = mock((..._args: any[]) => {});
|
|
const orig = console.log;
|
|
console.log = spy;
|
|
return { orig, restore: () => (console.log = orig), spy };
|
|
}
|
|
|
|
describe("useProjects onSuccess 日志", () => {
|
|
const qc = makeQueryClient();
|
|
|
|
test("create onSuccess", async () => {
|
|
setupProjectFetches(PROJECT);
|
|
const { restore, spy } = spyConsoleLog();
|
|
|
|
function T({ onResult }: { onResult: (fn: () => void) => void }) {
|
|
const { mutate } = useCreateProject();
|
|
const c = useRef(false);
|
|
if (!c.current) {
|
|
c.current = true;
|
|
onResult(() => mutate({ name: "x" }));
|
|
}
|
|
return null;
|
|
}
|
|
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
|
|
await new Promise((r) => setTimeout(r, 200));
|
|
|
|
const msgs = getLogMessages(spy);
|
|
expect(msgs).toHaveLength(1);
|
|
expect(msgs[0]).toMatch(/项目创建成功/);
|
|
restore();
|
|
});
|
|
|
|
test("update onSuccess", async () => {
|
|
setupProjectFetches(PROJECT);
|
|
const { restore, spy } = spyConsoleLog();
|
|
|
|
function T({ onResult }: { onResult: (fn: () => void) => void }) {
|
|
const { mutate } = useUpdateProject();
|
|
const c = useRef(false);
|
|
if (!c.current) {
|
|
c.current = true;
|
|
onResult(() => mutate({ data: { name: "y" }, id: "p1" }));
|
|
}
|
|
return null;
|
|
}
|
|
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
|
|
await new Promise((r) => setTimeout(r, 200));
|
|
|
|
const msgs = getLogMessages(spy);
|
|
expect(msgs).toHaveLength(1);
|
|
expect(msgs[0]).toMatch(/项目更新成功/);
|
|
restore();
|
|
});
|
|
|
|
test("delete onSuccess", async () => {
|
|
setupProjectFetches(PROJECT);
|
|
const { restore, spy } = spyConsoleLog();
|
|
|
|
function T({ onResult }: { onResult: (fn: () => void) => void }) {
|
|
const { mutate } = useDeleteProject();
|
|
const c = useRef(false);
|
|
if (!c.current) {
|
|
c.current = true;
|
|
onResult(() => mutate("p1"));
|
|
}
|
|
return null;
|
|
}
|
|
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
|
|
await new Promise((r) => setTimeout(r, 200));
|
|
|
|
const msgs = getLogMessages(spy);
|
|
expect(msgs).toHaveLength(1);
|
|
expect(msgs[0]).toMatch(/项目删除成功/);
|
|
restore();
|
|
});
|
|
|
|
test("archive onSuccess", async () => {
|
|
setupProjectFetches(PROJECT);
|
|
const { restore, spy } = spyConsoleLog();
|
|
|
|
function T({ onResult }: { onResult: (fn: () => void) => void }) {
|
|
const { mutate } = useArchiveProject();
|
|
const c = useRef(false);
|
|
if (!c.current) {
|
|
c.current = true;
|
|
onResult(() => mutate("p1"));
|
|
}
|
|
return null;
|
|
}
|
|
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
|
|
await new Promise((r) => setTimeout(r, 200));
|
|
|
|
const msgs = getLogMessages(spy);
|
|
expect(msgs).toHaveLength(1);
|
|
expect(msgs[0]).toMatch(/项目归档成功/);
|
|
restore();
|
|
});
|
|
|
|
test("restore onSuccess", async () => {
|
|
setupProjectFetches(PROJECT);
|
|
const { restore, spy } = spyConsoleLog();
|
|
|
|
function T({ onResult }: { onResult: (fn: () => void) => void }) {
|
|
const { mutate } = useRestoreProject();
|
|
const c = useRef(false);
|
|
if (!c.current) {
|
|
c.current = true;
|
|
onResult(() => mutate("p1"));
|
|
}
|
|
return null;
|
|
}
|
|
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
|
|
await new Promise((r) => setTimeout(r, 200));
|
|
|
|
const msgs = getLogMessages(spy);
|
|
expect(msgs).toHaveLength(1);
|
|
expect(msgs[0]).toMatch(/项目恢复成功/);
|
|
restore();
|
|
});
|
|
});
|
|
|
|
describe("useModels onSuccess 日志", () => {
|
|
const qc = makeQueryClient();
|
|
|
|
test("create onSuccess", async () => {
|
|
setupModelFetches(MODEL);
|
|
const { restore, spy } = spyConsoleLog();
|
|
|
|
function T({ onResult }: { onResult: (fn: () => void) => void }) {
|
|
const { mutate } = useCreateModel();
|
|
const c = useRef(false);
|
|
if (!c.current) {
|
|
c.current = true;
|
|
onResult(() => mutate({ capabilities: ["text"], modelId: "gpt-4", name: "x", providerId: "p1" }));
|
|
}
|
|
return null;
|
|
}
|
|
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
|
|
await new Promise((r) => setTimeout(r, 200));
|
|
|
|
const msgs = getLogMessages(spy);
|
|
expect(msgs).toHaveLength(1);
|
|
expect(msgs[0]).toMatch(/模型创建成功/);
|
|
restore();
|
|
});
|
|
|
|
test("update onSuccess", async () => {
|
|
setupModelFetches(MODEL);
|
|
const { restore, spy } = spyConsoleLog();
|
|
|
|
function T({ onResult }: { onResult: (fn: () => void) => void }) {
|
|
const { mutate } = useUpdateModel();
|
|
const c = useRef(false);
|
|
if (!c.current) {
|
|
c.current = true;
|
|
onResult(() => mutate({ data: { name: "y" }, id: "m1" }));
|
|
}
|
|
return null;
|
|
}
|
|
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
|
|
await new Promise((r) => setTimeout(r, 200));
|
|
|
|
const msgs = getLogMessages(spy);
|
|
expect(msgs).toHaveLength(1);
|
|
expect(msgs[0]).toMatch(/模型更新成功/);
|
|
restore();
|
|
});
|
|
|
|
test("delete onSuccess", async () => {
|
|
setupModelFetches(MODEL);
|
|
const { restore, spy } = spyConsoleLog();
|
|
|
|
function T({ onResult }: { onResult: (fn: () => void) => void }) {
|
|
const { mutate } = useDeleteModel();
|
|
const c = useRef(false);
|
|
if (!c.current) {
|
|
c.current = true;
|
|
onResult(() => mutate("m1"));
|
|
}
|
|
return null;
|
|
}
|
|
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
|
|
await new Promise((r) => setTimeout(r, 200));
|
|
|
|
const msgs = getLogMessages(spy);
|
|
expect(msgs).toHaveLength(1);
|
|
expect(msgs[0]).toMatch(/模型删除成功/);
|
|
restore();
|
|
});
|
|
|
|
test("test onSuccess", async () => {
|
|
setupModelFetches(MODEL);
|
|
const { restore, spy } = spyConsoleLog();
|
|
|
|
function T({ onResult }: { onResult: (fn: () => void) => void }) {
|
|
const { mutate } = useTestModelConnection();
|
|
const c = useRef(false);
|
|
if (!c.current) {
|
|
c.current = true;
|
|
onResult(() => mutate({ modelId: "gpt-4", providerId: "p1" }));
|
|
}
|
|
return null;
|
|
}
|
|
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
|
|
await new Promise((r) => setTimeout(r, 200));
|
|
|
|
restore();
|
|
// useTestModelConnection has no onSuccess logger
|
|
const infoCalls = spy.mock.calls.filter((c) => typeof c[0] === "string" && c[0].includes("[Alfred:INFO]"));
|
|
expect(infoCalls.length).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("useProviders onSuccess 日志", () => {
|
|
const qc = makeQueryClient();
|
|
|
|
test("create onSuccess", async () => {
|
|
setupProviderFetches(PROVIDER);
|
|
const { restore, spy } = spyConsoleLog();
|
|
|
|
function T({ onResult }: { onResult: (fn: () => void) => void }) {
|
|
const { mutate } = useCreateProvider();
|
|
const c = useRef(false);
|
|
if (!c.current) {
|
|
c.current = true;
|
|
onResult(() => mutate({ apiKey: "k", baseUrl: "http://x", name: "x", type: "openai" }));
|
|
}
|
|
return null;
|
|
}
|
|
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
|
|
await new Promise((r) => setTimeout(r, 200));
|
|
|
|
const msgs = getLogMessages(spy);
|
|
expect(msgs).toHaveLength(1);
|
|
expect(msgs[0]).toMatch(/供应商创建成功/);
|
|
restore();
|
|
});
|
|
|
|
test("update onSuccess", async () => {
|
|
setupProviderFetches(PROVIDER);
|
|
const { restore, spy } = spyConsoleLog();
|
|
|
|
function T({ onResult }: { onResult: (fn: () => void) => void }) {
|
|
const { mutate } = useUpdateProvider();
|
|
const c = useRef(false);
|
|
if (!c.current) {
|
|
c.current = true;
|
|
onResult(() => mutate({ data: { name: "y" }, id: "prov-1" }));
|
|
}
|
|
return null;
|
|
}
|
|
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
|
|
await new Promise((r) => setTimeout(r, 200));
|
|
|
|
const msgs = getLogMessages(spy);
|
|
expect(msgs).toHaveLength(1);
|
|
expect(msgs[0]).toMatch(/供应商更新成功/);
|
|
restore();
|
|
});
|
|
|
|
test("delete onSuccess", async () => {
|
|
setupProviderFetches(PROVIDER);
|
|
const { restore, spy } = spyConsoleLog();
|
|
|
|
function T({ onResult }: { onResult: (fn: () => void) => void }) {
|
|
const { mutate } = useDeleteProvider();
|
|
const c = useRef(false);
|
|
if (!c.current) {
|
|
c.current = true;
|
|
onResult(() => mutate("prov-1"));
|
|
}
|
|
return null;
|
|
}
|
|
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
|
|
await new Promise((r) => setTimeout(r, 200));
|
|
|
|
const msgs = getLogMessages(spy);
|
|
expect(msgs).toHaveLength(1);
|
|
expect(msgs[0]).toMatch(/供应商删除成功/);
|
|
restore();
|
|
});
|
|
|
|
test("test onSuccess", async () => {
|
|
setupProviderFetches(PROVIDER);
|
|
const { restore, spy } = spyConsoleLog();
|
|
|
|
function T({ onResult }: { onResult: (fn: () => void) => void }) {
|
|
const { mutate } = useTestProviderConfig();
|
|
const c = useRef(false);
|
|
if (!c.current) {
|
|
c.current = true;
|
|
onResult(() => mutate({ apiKey: "k", baseUrl: "http://x", name: "x", type: "openai" }));
|
|
}
|
|
return null;
|
|
}
|
|
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
|
|
await new Promise((r) => setTimeout(r, 200));
|
|
|
|
restore();
|
|
// useTestProviderConfig has no onSuccess logger
|
|
const infoMsgs = spy.mock.calls.filter((c) => typeof c[0] === "string" && String(c[0]).includes("[Alfred:INFO]"));
|
|
expect(infoMsgs.length).toBe(0);
|
|
});
|
|
});
|