Files
ttstd-infinite-canvas/web/test/local-channel-permission.test.ts
T

131 lines
7.3 KiB
TypeScript

import { afterEach, describe, expect, test } from "bun:test";
import { desktopLocalChannelUiVisible } from "../src/lib/desktop-local-channel";
import { fetchChannelModels } from "../src/services/api/image";
import { channelRequest } from "../src/services/api/custom-channel-relay";
import { backendProviderConfig } from "../src/services/api/generation-task";
import { apiClient } from "../src/services/api/request";
import { channelConnectionSignature, createModelChannel, defaultConfig, normalizeConfigSnapshot, resolveModelRequestConfig, type ModelChannel } from "../src/stores/use-config-store";
import { defaultFeatureAvailability, useUserStore } from "../src/stores/use-user-store";
const originalApiRequest = apiClient.request;
const originalWindow = globalThis.window;
afterEach(() => {
apiClient.request = originalApiRequest;
useUserStore.setState({ features: defaultFeatureAvailability });
if (originalWindow === undefined) delete (globalThis as { window?: Window }).window;
else Object.defineProperty(globalThis, "window", { configurable: true, value: originalWindow });
});
function setLocalChannelRuntime(desktopLocalChannelsEnabled: boolean, hostname: string) {
useUserStore.setState({ features: { ...defaultFeatureAvailability, desktopLocalChannelsEnabled } });
Object.defineProperty(globalThis, "window", { configurable: true, value: { location: { hostname } } });
}
function forgedLocalGenerationConfig() {
const channel = createModelChannel({
id: "local",
name: "Local",
baseUrl: "http://127.0.0.1:8000",
apiKey: "test-key",
apiFormat: "openai",
models: ["local-model"],
allowLocalChannel: true,
});
return normalizeConfigSnapshot({ config: { ...defaultConfig, channels: [channel], model: "local::local-model", textModel: "local::local-model" } }).config;
}
describe("允许本机渠道前端请求合同", () => {
test("默认关闭,旧持久化记录回退 false,新记录 true 可回显并进入请求配置", () => {
const legacy = createModelChannel({ id: "legacy", name: "Legacy", baseUrl: "https://example.com", apiKey: "key", models: ["model"] });
expect(legacy.allowLocalChannel).toBe(false);
const local = createModelChannel({ id: "local", name: "Local", baseUrl: "http://127.0.0.1:8000", apiKey: "key", models: ["model"], allowLocalChannel: true });
const normalized = normalizeConfigSnapshot({ config: { ...defaultConfig, channels: [local], model: "local::model", textModel: "local::model" } }).config;
expect(normalized.channels[0]?.allowLocalChannel).toBe(true);
setLocalChannelRuntime(true, "127.0.0.1");
expect(resolveModelRequestConfig(normalized, "local::model").allowLocalChannel).toBe(true);
setLocalChannelRuntime(false, "127.0.0.1");
expect(resolveModelRequestConfig(normalized, "local::model").allowLocalChannel).toBe(false);
});
test("UI 只在 Backend capability=true 且页面文本 host 精确 loopback 时显示", () => {
expect(desktopLocalChannelUiVisible(true, "127.0.0.1")).toBe(true);
expect(desktopLocalChannelUiVisible(true, "localhost")).toBe(true);
for (const hostname of ["localhost.", "x.localhost", "127.0.0.2", "::1", "0.0.0.0", "192.168.1.5", "canvas.example.com"]) {
expect(desktopLocalChannelUiVisible(true, hostname)).toBe(false);
}
expect(desktopLocalChannelUiVisible(false, "127.0.0.1")).toBe(false);
expect(desktopLocalChannelUiVisible(false, "localhost")).toBe(false);
});
test("channelConnectionSignature 包含 allowLocalChannel,切换后旧模型拉取结果会失效", () => {
const channel = createModelChannel({ id: "local", name: "Local", baseUrl: "http://127.0.0.1:8000", apiKey: "key", models: ["model"] });
expect(channelConnectionSignature(channel)).not.toBe(channelConnectionSignature({ ...channel, allowLocalChannel: true }));
});
test("实际生成配置与 relay 在运行时重新投影 forged persisted local flag", () => {
const config = forgedLocalGenerationConfig();
setLocalChannelRuntime(false, "127.0.0.1");
const capabilityClosed = backendProviderConfig(config);
expect(capabilityClosed.allowLocalChannel).toBe(false);
expect(channelRequest(capabilityClosed, "http://127.0.0.1:8000/v1/responses").headers["x-canvas-allow-local-channel"]).toBeUndefined();
setLocalChannelRuntime(true, "canvas.example.com");
const remotePage = backendProviderConfig(config);
expect(remotePage.allowLocalChannel).toBe(false);
expect(channelRequest(remotePage, "http://127.0.0.1:8000/v1/responses").headers["x-canvas-allow-local-channel"]).toBeUndefined();
setLocalChannelRuntime(true, "localhost");
const desktop = backendProviderConfig(config);
expect(desktop.allowLocalChannel).toBe(true);
const request = channelRequest(desktop, "http://127.0.0.1:8000/v1/responses", { "Content-Type": "application/json" });
expect(request.headers["x-canvas-allow-local-channel"]).toBe("1");
expect(request.headers["x-canvas-upstream-base-url"]).toBe("http://127.0.0.1:8000/");
});
test("登录态模型拉取同样消费运行时投影而不是 forged persisted flag", async () => {
const bodies: Record<string, unknown>[] = [];
apiClient.request = (async (request) => {
bodies.push(request.data as Record<string, unknown>);
return { data: { code: 0, data: { models: [{ id: "local-model" }] }, msg: "ok" } };
}) as typeof apiClient.request;
const channel: ModelChannel = forgedLocalGenerationConfig().channels[0];
setLocalChannelRuntime(false, "127.0.0.1");
await fetchChannelModels(channel, true);
expect(bodies.at(-1)?.allowLocalChannel).toBe(false);
setLocalChannelRuntime(true, "canvas.example.com");
await fetchChannelModels(channel, true);
expect(bodies.at(-1)?.allowLocalChannel).toBe(false);
setLocalChannelRuntime(true, "127.0.0.1");
await fetchChannelModels(channel, true);
expect(bodies.at(-1)?.allowLocalChannel).toBe(true);
});
test("custom relay 最终边界也会压掉直接伪造的 local flag", () => {
const config = {
baseUrl: "http://127.0.0.1:8000",
apiKey: "test-key",
apiFormat: "openai" as const,
allowLocalChannel: true,
};
setLocalChannelRuntime(false, "127.0.0.1");
expect(channelRequest(config, "http://127.0.0.1:8000/v1/responses").headers["x-canvas-allow-local-channel"]).toBeUndefined();
setLocalChannelRuntime(true, "localhost");
expect(channelRequest(config, "http://127.0.0.1:8000/v1/responses").headers["x-canvas-allow-local-channel"]).toBe("1");
});
test("非法自定义渠道 Base URL 返回可读错误,而不是原生 URL 异常", () => {
expect(() => channelRequest({ baseUrl: "api.example.com/v1", apiKey: "key", apiFormat: "openai" }, "api.example.com/v1/images/generations")).toThrow("当前模型渠道 Base URL 无效");
});
test("非法上游请求地址返回可读错误", () => {
expect(() => channelRequest({ baseUrl: "https://api.example.com/v1", apiKey: "key", apiFormat: "openai" }, "api.example.com/v1/images/generations")).toThrow("当前模型请求地址 无效");
});
});