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

132 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { afterEach, describe, expect, test } from "bun:test";
import axios from "axios";
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 { channelConnectionSignature, createModelChannel, defaultConfig, normalizeConfigSnapshot, resolveModelRequestConfig, type ModelChannel } from "../src/stores/use-config-store";
import { defaultFeatureAvailability, useUserStore } from "../src/stores/use-user-store";
const originalAxiosPost = axios.post;
const originalWindow = globalThis.window;
afterEach(() => {
axios.post = originalAxiosPost;
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>[] = [];
axios.post = (async (_url: string, body: Record<string, unknown>) => {
bodies.push(body);
return { data: { code: 0, data: { models: [{ id: "local-model" }] }, msg: "ok" } };
}) as typeof axios.post;
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("当前模型请求地址 无效");
});
});