mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
418 lines
14 KiB
TypeScript
418 lines
14 KiB
TypeScript
import { expect, test } from "vite-plus/test";
|
||
import { parseConfigFile, type ConfigFile, type Settings } from "../src/config/schema.ts";
|
||
import { buildSettings, type ResolutionSources } from "../src/config/loader.ts";
|
||
import {
|
||
resolveApiKey,
|
||
resolveConsole,
|
||
resolveModelBaseUrl,
|
||
resolveModelBaseUrlState,
|
||
resolveOpenApi,
|
||
} from "../src/auth/resolver.ts";
|
||
import { getModelProfilePreset } from "../src/config/profile-presets.ts";
|
||
|
||
// 行为锁定:所有配置字段统一保持 flag>env>selected file>默认。`--config` 只选择
|
||
// file block,不提升该 block 的字段优先级。Profile 预设只在登录写入阶段使用。
|
||
// buildSettings 与 resolver 都是纯函数,sources 直接构造,无需环境隔离。
|
||
|
||
function src(s: {
|
||
flags?: ResolutionSources["flags"];
|
||
env?: Record<string, string>;
|
||
file?: ConfigFile;
|
||
configName?: string;
|
||
}): ResolutionSources {
|
||
return {
|
||
flags: s.flags ?? {},
|
||
file: s.file ?? {},
|
||
env: s.env ?? {},
|
||
configName: s.configName,
|
||
};
|
||
}
|
||
|
||
const resolve = (s: Parameters<typeof src>[0]): Settings => buildSettings(src(s));
|
||
|
||
test("token-plan Profile 预设保持固定", () => {
|
||
expect(getModelProfilePreset("token-plan")).toEqual({
|
||
baseUrl: "https://token-plan.cn-beijing.maas.aliyuncs.com",
|
||
defaultTextModel: "qwen3.8-max",
|
||
defaultVideoModel: "happyhorse-1.1-t2v",
|
||
defaultImageToVideoModel: "happyhorse-1.1-i2v",
|
||
defaultReferenceToVideoModel: "happyhorse-1.1-r2v",
|
||
defaultImageModel: "wan2.7-image",
|
||
defaultSpeechModel: "qwen-audio-3.0-tts-plus",
|
||
defaultSpeechRecognitionModel: "qwen-audio-3.0-asr-flash",
|
||
apiKeyCapabilities: [
|
||
"text.chat",
|
||
"vision.describe",
|
||
"image.generate",
|
||
"image.edit",
|
||
"speech.recognize",
|
||
"speech.synthesize",
|
||
"video.generate",
|
||
"video.ref",
|
||
"video.task.get",
|
||
"video.download",
|
||
],
|
||
});
|
||
});
|
||
|
||
test("api_key_capabilities 解析并去重叶子能力,保留显式空白名单", () => {
|
||
expect(
|
||
parseConfigFile({
|
||
api_key_capabilities: [" text.chat ", "image.generate", "text.chat", "video.task.get"],
|
||
}).api_key_capabilities,
|
||
).toEqual(["text.chat", "image.generate", "video.task.get"]);
|
||
expect(parseConfigFile({ api_key_capabilities: [] }).api_key_capabilities).toEqual([]);
|
||
expect(parseConfigFile({}).api_key_capabilities).toBeUndefined();
|
||
});
|
||
|
||
test("api_key_capabilities 手工配置格式错误时 fail closed", () => {
|
||
expect(parseConfigFile({ api_key_capabilities: "text.chat" }).api_key_capabilities).toEqual([]);
|
||
expect(
|
||
parseConfigFile({ api_key_capabilities: ["text.chat", "Video Generate"] }).api_key_capabilities,
|
||
).toEqual([]);
|
||
});
|
||
|
||
test("default_speech_recognition_model 从配置文件进入运行时 Settings", () => {
|
||
const file = parseConfigFile({
|
||
default_speech_recognition_model: "qwen-audio-3.0-asr-flash",
|
||
});
|
||
expect(file.default_speech_recognition_model).toBe("qwen-audio-3.0-asr-flash");
|
||
expect(resolve({ file }).defaultSpeechRecognitionModel).toBe("qwen-audio-3.0-asr-flash");
|
||
});
|
||
|
||
test("watermark 从配置文件进入 Settings,缺省时保持合规默认值 true", () => {
|
||
expect(parseConfigFile({ watermark: false }).watermark).toBe(false);
|
||
expect(parseConfigFile({ watermark: true }).watermark).toBe(true);
|
||
expect(parseConfigFile({ watermark: "false" }).watermark).toBeUndefined();
|
||
expect(resolve({ file: { watermark: false } }).watermark).toBe(false);
|
||
expect(resolve({}).watermark).toBe(true);
|
||
});
|
||
|
||
test("baseUrl:flag > env > file > 默认,所有来源统一归一化", () => {
|
||
const flags = { baseUrl: "https://flag.example.com/compatible-mode/v1?source=flag" };
|
||
const env = { DASHSCOPE_BASE_URL: "https://env.example.com/apps/anthropic#env" };
|
||
const file: ConfigFile = { base_url: "https://file.example.com/gateway/" };
|
||
expect(resolveModelBaseUrl(src({ flags, env, file }))).toBe("https://flag.example.com");
|
||
expect(resolveModelBaseUrl(src({ env, file }))).toBe("https://env.example.com");
|
||
expect(resolveModelBaseUrl(src({ file }))).toBe("https://file.example.com");
|
||
expect(resolveModelBaseUrl(src({}))).toBe("https://dashscope.aliyuncs.com");
|
||
});
|
||
|
||
test("baseUrl:非法 flag/env 在 resolver 边界报 usage error", () => {
|
||
expect(() => resolveModelBaseUrl(src({ flags: { baseUrl: "not-a-url" } }))).toThrow(
|
||
/Invalid model base URL/,
|
||
);
|
||
expect(() =>
|
||
resolveModelBaseUrl(src({ env: { DASHSCOPE_BASE_URL: "file:///tmp/model" } })),
|
||
).toThrow(/Invalid model base URL/);
|
||
});
|
||
|
||
test("baseUrl state distinguishes an implicit default from an explicitly configured default origin", () => {
|
||
const defaultOrigin = "https://dashscope.aliyuncs.com";
|
||
expect(resolveModelBaseUrlState(src({}))).toEqual({
|
||
baseUrl: defaultOrigin,
|
||
baseUrlIsDefault: true,
|
||
});
|
||
for (const sources of [
|
||
src({ flags: { baseUrl: defaultOrigin } }),
|
||
src({ env: { DASHSCOPE_BASE_URL: defaultOrigin } }),
|
||
src({ file: { base_url: defaultOrigin } }),
|
||
]) {
|
||
expect(resolveModelBaseUrlState(sources)).toEqual({
|
||
baseUrl: defaultOrigin,
|
||
baseUrlIsDefault: false,
|
||
});
|
||
}
|
||
});
|
||
|
||
test("命名 config 仍保持 flag > env > selected file", () => {
|
||
const env = {
|
||
DASHSCOPE_BASE_URL: "https://env.example.com",
|
||
DASHSCOPE_API_KEY: "sk-env",
|
||
};
|
||
const sources = src({
|
||
configName: "token-plan",
|
||
env,
|
||
file: {
|
||
api_key: "sk-token-plan",
|
||
base_url: "https://profile.example.com",
|
||
default_text_model: "custom-text",
|
||
default_image_model: "custom-image",
|
||
},
|
||
});
|
||
expect(resolveModelBaseUrl(sources)).toBe("https://env.example.com");
|
||
expect(resolveApiKey(sources)).toMatchObject({
|
||
token: "sk-env",
|
||
baseUrl: "https://env.example.com",
|
||
source: "env",
|
||
});
|
||
expect(buildSettings(sources)).toMatchObject({
|
||
defaultTextModel: "custom-text",
|
||
defaultImageModel: "custom-image",
|
||
});
|
||
expect(
|
||
resolveApiKey(
|
||
src({
|
||
configName: "token-plan",
|
||
flags: { apiKey: "sk-flag", baseUrl: "https://flag.example.com" },
|
||
env,
|
||
file: sources.file,
|
||
}),
|
||
),
|
||
).toMatchObject({
|
||
token: "sk-flag",
|
||
baseUrl: "https://flag.example.com",
|
||
source: "flag",
|
||
});
|
||
});
|
||
|
||
test("output:flag > env > file > text", () => {
|
||
const env = { DASHSCOPE_OUTPUT: "json" };
|
||
const file: ConfigFile = { output: "json" };
|
||
expect(resolve({ flags: { output: "text" }, env, file }).output).toBe("text");
|
||
expect(resolve({ env, file: { output: "text" } }).output).toBe("json");
|
||
expect(resolve({ file }).output).toBe("json");
|
||
expect(resolve({}).output).toBe("text");
|
||
});
|
||
|
||
test("timeout:flag > 合法 env > file > 300;非法 env 被跳过;非法 flag 抛错", () => {
|
||
const file: ConfigFile = { timeout: 30 };
|
||
expect(resolve({ flags: { timeout: 10 }, env: { DASHSCOPE_TIMEOUT: "20" }, file }).timeout).toBe(
|
||
10,
|
||
);
|
||
expect(resolve({ env: { DASHSCOPE_TIMEOUT: "20" }, file }).timeout).toBe(20);
|
||
expect(resolve({ env: { DASHSCOPE_TIMEOUT: "abc" }, file }).timeout).toBe(30);
|
||
expect(resolve({ env: { DASHSCOPE_TIMEOUT: "-5" }, file }).timeout).toBe(30);
|
||
expect(resolve({}).timeout).toBe(300);
|
||
expect(() => resolve({ flags: { timeout: -1 } })).toThrow(/Timeout/);
|
||
});
|
||
|
||
test("workspaceId:flag > env > file(console 域 flag)", () => {
|
||
const file: ConfigFile = { workspace_id: "ws-file" };
|
||
const env = { BAILIAN_WORKSPACE_ID: "ws-env" };
|
||
expect(resolve({ flags: { workspaceId: "ws-flag" }, env, file }).workspaceId).toBe("ws-flag");
|
||
expect(resolve({ env, file }).workspaceId).toBe("ws-env");
|
||
expect(resolve({ file }).workspaceId).toBe("ws-file");
|
||
expect(resolve({}).workspaceId).toBeUndefined();
|
||
});
|
||
|
||
test("console 三元组:flag > file,无兜底(默认值由 gateway 层兜)", () => {
|
||
const file: ConfigFile = {
|
||
console_region: "cn-shanghai",
|
||
console_site: "international",
|
||
console_switch_agent: 111,
|
||
};
|
||
const fromFlags = resolve({
|
||
flags: { consoleRegion: "ap-southeast-1", consoleSite: "domestic", consoleSwitchAgent: 222 },
|
||
file,
|
||
});
|
||
expect(fromFlags.consoleRegion).toBe("ap-southeast-1");
|
||
expect(fromFlags.consoleSite).toBe("domestic");
|
||
expect(fromFlags.consoleSwitchAgent).toBe(222);
|
||
const fromFile = resolve({ file });
|
||
expect(fromFile.consoleRegion).toBe("cn-shanghai");
|
||
expect(fromFile.consoleSite).toBe("international");
|
||
expect(fromFile.consoleSwitchAgent).toBe(111);
|
||
expect(resolve({}).consoleRegion).toBeUndefined();
|
||
expect(resolve({}).consoleSite).toBeUndefined();
|
||
});
|
||
|
||
test("verbose:flag 或 DASHSCOPE_VERBOSE=1(无 file 源;env 非 1 不生效)", () => {
|
||
expect(resolve({ flags: { verbose: true } }).verbose).toBe(true);
|
||
expect(resolve({ env: { DASHSCOPE_VERBOSE: "1" } }).verbose).toBe(true);
|
||
expect(resolve({ env: { DASHSCOPE_VERBOSE: "0" } }).verbose).toBe(false);
|
||
expect(resolve({}).verbose).toBe(false);
|
||
});
|
||
|
||
test("telemetry:DO_NOT_TRACK=1 一票否决 > file > 默认 true", () => {
|
||
expect(resolve({ env: { DO_NOT_TRACK: "1" }, file: { telemetry: true } }).telemetry).toBe(false);
|
||
expect(resolve({ file: { telemetry: false } }).telemetry).toBe(false);
|
||
expect(resolve({}).telemetry).toBe(true);
|
||
});
|
||
|
||
test("apiKey 凭证:flag > env > file,source 字段随之;无 key 抛 AUTH", () => {
|
||
const all = src({
|
||
flags: { apiKey: "sk-flag" },
|
||
env: { DASHSCOPE_API_KEY: "sk-env" },
|
||
file: { api_key: "sk-file" },
|
||
});
|
||
expect(resolveApiKey(all)).toMatchObject({ token: "sk-flag", source: "flag" });
|
||
const envFile = src({ env: { DASHSCOPE_API_KEY: "sk-env" }, file: { api_key: "sk-file" } });
|
||
expect(resolveApiKey(envFile)).toMatchObject({ token: "sk-env", source: "env" });
|
||
const fileOnly = src({ file: { api_key: "sk-file" } });
|
||
expect(resolveApiKey(fileOnly)).toMatchObject({ token: "sk-file", source: "config" });
|
||
expect(() => resolveApiKey(src({}))).toThrow(/No API key/);
|
||
});
|
||
|
||
test("console 凭证:token 仅 file 源;目标 flag > file > 默认;无 token 抛 AUTH", () => {
|
||
const cred = resolveConsole(
|
||
src({
|
||
flags: { consoleRegion: "ap-southeast-1" },
|
||
file: { access_token: "tok", console_site: "international", console_switch_agent: 7 },
|
||
}),
|
||
);
|
||
expect(cred).toMatchObject({
|
||
token: "tok",
|
||
region: "ap-southeast-1",
|
||
site: "international",
|
||
switchAgent: 7,
|
||
});
|
||
expect(resolveConsole(src({ file: { access_token: "tok" } }))).toMatchObject({
|
||
region: "cn-beijing",
|
||
site: "domestic",
|
||
});
|
||
expect(() => resolveConsole(src({}))).toThrow(/console access token/);
|
||
});
|
||
|
||
test("openapi 凭证:按来源成对解析,flag > env > file;缺 id 或 secret 抛 AUTH", () => {
|
||
const all = src({
|
||
flags: { accessKeyId: "ak-flag", accessKeySecret: "secret-flag" },
|
||
env: {
|
||
ALIBABA_CLOUD_ACCESS_KEY_ID: "ak-env",
|
||
ALIBABA_CLOUD_ACCESS_KEY_SECRET: "secret-env",
|
||
},
|
||
});
|
||
expect(resolveOpenApi(all)).toMatchObject({
|
||
accessKeyId: "ak-flag",
|
||
accessKeySecret: "secret-flag",
|
||
source: "flag",
|
||
});
|
||
|
||
const envOnly = src({
|
||
env: {
|
||
ALIBABA_CLOUD_ACCESS_KEY_ID: "ak-env",
|
||
ALIBABA_CLOUD_ACCESS_KEY_SECRET: "secret-env",
|
||
},
|
||
});
|
||
expect(resolveOpenApi(envOnly)).toMatchObject({
|
||
accessKeyId: "ak-env",
|
||
accessKeySecret: "secret-env",
|
||
source: "env",
|
||
});
|
||
|
||
const fileOnly = src({
|
||
file: {
|
||
access_key_id: "ak-file",
|
||
access_key_secret: "secret-file",
|
||
},
|
||
});
|
||
expect(resolveOpenApi(fileOnly)).toMatchObject({
|
||
accessKeyId: "ak-file",
|
||
accessKeySecret: "secret-file",
|
||
source: "config",
|
||
});
|
||
|
||
const legacyFileOnly = src({
|
||
file: parseConfigFile({
|
||
openapi_access_key_id: "ak-legacy-file",
|
||
openapi_access_key_secret: "secret-legacy-file",
|
||
}),
|
||
});
|
||
expect(resolveOpenApi(legacyFileOnly)).toMatchObject({
|
||
accessKeyId: "ak-legacy-file",
|
||
accessKeySecret: "secret-legacy-file",
|
||
source: "config",
|
||
});
|
||
|
||
expect(() =>
|
||
resolveOpenApi(
|
||
src({
|
||
flags: { accessKeyId: "ak-flag" },
|
||
env: {
|
||
ALIBABA_CLOUD_ACCESS_KEY_ID: "ak-env",
|
||
ALIBABA_CLOUD_ACCESS_KEY_SECRET: "secret-env",
|
||
},
|
||
file: {
|
||
access_key_id: "ak-file",
|
||
access_key_secret: "secret-file",
|
||
},
|
||
}),
|
||
),
|
||
).toThrow(/Incomplete OpenAPI AK\/SK/);
|
||
|
||
expect(() =>
|
||
resolveOpenApi(
|
||
src({
|
||
env: { ALIBABA_CLOUD_ACCESS_KEY_SECRET: "secret-env" },
|
||
file: {
|
||
access_key_id: "ak-file",
|
||
access_key_secret: "secret-file",
|
||
},
|
||
}),
|
||
),
|
||
).toThrow(/Incomplete OpenAPI AK\/SK/);
|
||
|
||
expect(() =>
|
||
resolveOpenApi(
|
||
src({
|
||
file: {
|
||
access_key_id: "ak-file",
|
||
},
|
||
}),
|
||
),
|
||
).toThrow(/Incomplete OpenAPI AK\/SK/);
|
||
|
||
expect(() => resolveOpenApi(src({}))).toThrow(/No OpenAPI AK\/SK/);
|
||
expect(() => resolveOpenApi(src({ env: { ALIBABA_CLOUD_ACCESS_KEY_ID: "ak-env" } }))).toThrow(
|
||
/Incomplete OpenAPI AK\/SK/,
|
||
);
|
||
});
|
||
|
||
test("openapi 凭证:低优先级来源缺字段时不影响更高优先级成对凭证", () => {
|
||
const completeFlagCred = src({
|
||
flags: { accessKeyId: "ak-flag", accessKeySecret: "secret-flag" },
|
||
env: { ALIBABA_CLOUD_ACCESS_KEY_SECRET: "secret-env" },
|
||
});
|
||
expect(resolveOpenApi(completeFlagCred)).toMatchObject({
|
||
accessKeyId: "ak-flag",
|
||
accessKeySecret: "secret-flag",
|
||
source: "flag",
|
||
});
|
||
|
||
const completeEnvCred = src({
|
||
env: {
|
||
ALIBABA_CLOUD_ACCESS_KEY_ID: "ak-env",
|
||
ALIBABA_CLOUD_ACCESS_KEY_SECRET: "secret-env",
|
||
},
|
||
file: { access_key_id: "ak-file" },
|
||
});
|
||
expect(resolveOpenApi(completeEnvCred)).toMatchObject({
|
||
accessKeyId: "ak-env",
|
||
accessKeySecret: "secret-env",
|
||
source: "env",
|
||
});
|
||
});
|
||
|
||
test("default*Model / outputDir:仅 file 源", () => {
|
||
const c = resolve({
|
||
file: parseConfigFile({
|
||
default_text_model: "qwen-max",
|
||
default_video_model: "wan-t2v",
|
||
default_image_to_video_model: "wan-i2v",
|
||
default_reference_to_video_model: "wan-r2v",
|
||
output_dir: "/tmp/out",
|
||
}),
|
||
});
|
||
expect(c.defaultTextModel).toBe("qwen-max");
|
||
expect(c.defaultVideoModel).toBe("wan-t2v");
|
||
expect(c.defaultImageToVideoModel).toBe("wan-i2v");
|
||
expect(c.defaultReferenceToVideoModel).toBe("wan-r2v");
|
||
expect(c.outputDir).toBe("/tmp/out");
|
||
expect(resolve({}).defaultTextModel).toBeUndefined();
|
||
});
|
||
|
||
test("quiet/dryRun:仅 flag 源,直通", () => {
|
||
const on = resolve({
|
||
flags: { quiet: true, dryRun: true },
|
||
});
|
||
expect(on).toMatchObject({
|
||
quiet: true,
|
||
dryRun: true,
|
||
});
|
||
const off = resolve({});
|
||
expect(off).toMatchObject({
|
||
quiet: false,
|
||
dryRun: false,
|
||
});
|
||
});
|