280 lines
9.0 KiB
TypeScript
280 lines
9.0 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import {
|
|
CANVAS_SKILL_TAGS_CONFIG_VERSION,
|
|
CANVAS_SKILL_TAGS_V6_ADDITIONS,
|
|
DEFAULT_CANVAS_SKILL_TAGS,
|
|
normalizeCanvasSkillTagSettings,
|
|
type CanvasSkillTag,
|
|
} from "@/lib/canvas-skill-tags";
|
|
import {
|
|
defaultProductPlanningApiConfig,
|
|
normalizeProductPlanningApiConfig,
|
|
PRODUCT_PLANNING_API_CONFIG_KEY,
|
|
type ProductPlanningApiConfig,
|
|
} from "@/lib/product-planning/config";
|
|
import {
|
|
defaultProductPlanningAiModelConfig,
|
|
normalizeProductPlanningAiModelConfig,
|
|
toProductPlanningOpenAIBaseUrl,
|
|
type ProductPlanningAiModelConfig,
|
|
} from "@/lib/product-planning/model-config";
|
|
test.describe.serial("/api/admin/settings", () => {
|
|
test("guest cannot read product planning system config", async ({ request }) => {
|
|
const response = await request.get(
|
|
`/api/admin/settings?key=${PRODUCT_PLANNING_API_CONFIG_KEY}`
|
|
);
|
|
|
|
expect(response.status()).toBe(401);
|
|
|
|
const payload = await response.json();
|
|
expect(payload).toMatchObject({
|
|
error: "未授权,请先登录",
|
|
});
|
|
});
|
|
|
|
test("product planning config normalization trims values and falls back to defaults", () => {
|
|
const defaults = defaultProductPlanningApiConfig();
|
|
const expectedValue: ProductPlanningApiConfig = {
|
|
...defaults,
|
|
oneboundBaseUrl: "https://api.example.com/onebound",
|
|
taobaoApiKey: "demo-key",
|
|
taobaoApiSecret: "demo-secret",
|
|
qaApiUrl: defaults.qaApiUrl,
|
|
qaToken: "qa-token",
|
|
saleApiUrl: "http://sales.example.com/api",
|
|
saleToken: "sale-token",
|
|
};
|
|
|
|
const normalized = normalizeProductPlanningApiConfig({
|
|
oneboundBaseUrl: " https://api.example.com/onebound/// ",
|
|
taobaoApiKey: " demo-key ",
|
|
taobaoApiSecret: " demo-secret ",
|
|
qaApiUrl: " ",
|
|
qaToken: " qa-token ",
|
|
saleApiUrl: " http://sales.example.com/api/// ",
|
|
saleToken: " sale-token ",
|
|
});
|
|
|
|
expect(normalized).toEqual(expectedValue);
|
|
});
|
|
|
|
test("product planning config normalization can reuse fallback secrets and urls", () => {
|
|
const defaults = defaultProductPlanningApiConfig();
|
|
const normalized = normalizeProductPlanningApiConfig(
|
|
{
|
|
oneboundBaseUrl: " ",
|
|
taobaoApiKey: "",
|
|
taobaoApiSecret: " custom-secret ",
|
|
detailPlusBaseUrl: "",
|
|
detailPlusInternalBaseUrl: " ",
|
|
detailPlusApiKey: "",
|
|
qaApiUrl: "",
|
|
qaToken: " ",
|
|
saleApiUrl: " ",
|
|
saleToken: "",
|
|
planningDbHost: " ",
|
|
planningDbPort: "",
|
|
planningDbDatabase: " ",
|
|
planningDbUser: "",
|
|
planningDbPassword: "",
|
|
planningDbCharset: " ",
|
|
},
|
|
{
|
|
oneboundBaseUrl: "https://fallback.example.com/root/",
|
|
taobaoApiKey: "fallback-key",
|
|
taobaoApiSecret: "fallback-secret",
|
|
detailPlusBaseUrl: "https://fallback.example.com/detail-plus/",
|
|
detailPlusInternalBaseUrl: "http://127.0.0.1:4000/detail-plus/",
|
|
detailPlusApiKey: "fallback-detail-plus-key",
|
|
qaApiUrl: "https://fallback.example.com/qa/",
|
|
qaToken: "fallback-qa-token",
|
|
saleApiUrl: "https://fallback.example.com/sale/",
|
|
saleToken: "fallback-sale-token",
|
|
planningDbHost: "db.example.com",
|
|
planningDbPort: "60023",
|
|
planningDbDatabase: "planning_database",
|
|
planningDbUser: "planning_user",
|
|
planningDbPassword: "planning-password",
|
|
planningDbCharset: "utf8mb4",
|
|
planningDbSsl: true,
|
|
}
|
|
);
|
|
|
|
expect(normalized).toEqual({
|
|
...defaults,
|
|
oneboundBaseUrl: "https://fallback.example.com/root",
|
|
taobaoApiKey: "fallback-key",
|
|
taobaoApiSecret: "custom-secret",
|
|
detailPlusBaseUrl: "https://fallback.example.com/detail-plus",
|
|
detailPlusInternalBaseUrl: "http://127.0.0.1:4000/detail-plus",
|
|
detailPlusApiKey: "fallback-detail-plus-key",
|
|
qaApiUrl: "https://fallback.example.com/qa",
|
|
qaToken: "fallback-qa-token",
|
|
saleApiUrl: "https://fallback.example.com/sale",
|
|
saleToken: "fallback-sale-token",
|
|
planningDbHost: "db.example.com",
|
|
planningDbPort: "60023",
|
|
planningDbDatabase: "planning_database",
|
|
planningDbUser: "planning_user",
|
|
planningDbPassword: "planning-password",
|
|
planningDbCharset: "utf8mb4",
|
|
planningDbSsl: true,
|
|
} satisfies ProductPlanningApiConfig);
|
|
});
|
|
|
|
test("product planning dedicated model config normalization trims values and keeps defaults", () => {
|
|
const defaults = defaultProductPlanningAiModelConfig();
|
|
const normalized = normalizeProductPlanningAiModelConfig({
|
|
enabled: true,
|
|
providerName: " Planning Qwen ",
|
|
baseUrl: " http://103.118.245.6:6081/// ",
|
|
apiKey: " secret-token ",
|
|
modelId: " custom-qwen3-vl ",
|
|
displayName: " 商品策划专用模型 ",
|
|
});
|
|
|
|
expect(normalized).toEqual({
|
|
enabled: true,
|
|
providerName: "Planning Qwen",
|
|
baseUrl: "http://103.118.245.6:6081",
|
|
apiKey: "secret-token",
|
|
modelId: "custom-qwen3-vl",
|
|
displayName: "商品策划专用模型",
|
|
} satisfies ProductPlanningAiModelConfig);
|
|
|
|
const fallbackNormalized = normalizeProductPlanningAiModelConfig({
|
|
enabled: false,
|
|
providerName: " ",
|
|
baseUrl: " ",
|
|
apiKey: " ",
|
|
modelId: "",
|
|
displayName: "",
|
|
});
|
|
|
|
expect(fallbackNormalized).toEqual(defaults);
|
|
});
|
|
|
|
test("product planning dedicated model base url can auto-complete v1 path", () => {
|
|
expect(toProductPlanningOpenAIBaseUrl("http://103.118.245.6:6081")).toBe(
|
|
"http://103.118.245.6:6081/v1"
|
|
);
|
|
expect(toProductPlanningOpenAIBaseUrl("http://103.118.245.6:6081/v1/")).toBe(
|
|
"http://103.118.245.6:6081/v1"
|
|
);
|
|
});
|
|
|
|
test("canvas skill tag upgrades preserve custom tags and append new managed skills", () => {
|
|
const customTags: CanvasSkillTag[] = [
|
|
{
|
|
id: "custom-claw-skill",
|
|
name: "自定义技能",
|
|
prompt: "只保留后台手动配置的技能。",
|
|
placeholder: "后台手动配置",
|
|
iconName: "message-circle",
|
|
},
|
|
];
|
|
|
|
const normalized = normalizeCanvasSkillTagSettings(
|
|
{
|
|
enabled: true,
|
|
title: "试试这些 Skills",
|
|
tags: customTags,
|
|
version: 1,
|
|
},
|
|
{ requireCurrentVersion: true }
|
|
);
|
|
|
|
expect(normalized.title).toBe("试试这些 Skills");
|
|
expect(normalized.tags).toEqual([
|
|
...customTags,
|
|
...CANVAS_SKILL_TAGS_V6_ADDITIONS,
|
|
]);
|
|
expect(
|
|
normalized.tags.some((tag) => tag.id === "promotion-diagnosis-db")
|
|
).toBe(false);
|
|
});
|
|
|
|
test("current canvas skill tag settings can intentionally remove managed skills", () => {
|
|
const customTags: CanvasSkillTag[] = [
|
|
{
|
|
id: "custom-claw-skill",
|
|
name: "自定义技能",
|
|
prompt: "只保留后台手动配置的技能。",
|
|
},
|
|
];
|
|
|
|
const normalized = normalizeCanvasSkillTagSettings(
|
|
{
|
|
enabled: true,
|
|
title: "Claw 快捷技能",
|
|
tags: customTags,
|
|
version: CANVAS_SKILL_TAGS_CONFIG_VERSION,
|
|
},
|
|
{ requireCurrentVersion: true }
|
|
);
|
|
|
|
expect(normalized.tags).toEqual(customTags);
|
|
});
|
|
|
|
test("canvas skill tag settings retain more than thirty configured skills", () => {
|
|
const configuredTags: CanvasSkillTag[] = Array.from({ length: 31 }, (_, index) => ({
|
|
id: `configured-skill-${index + 1}`,
|
|
name: `Configured skill ${index + 1}`,
|
|
prompt: `Run configured skill ${index + 1}`,
|
|
}));
|
|
|
|
const normalized = normalizeCanvasSkillTagSettings({
|
|
enabled: true,
|
|
title: "Claw 快捷技能",
|
|
tags: configuredTags,
|
|
version: CANVAS_SKILL_TAGS_CONFIG_VERSION,
|
|
});
|
|
|
|
expect(normalized.tags).toEqual(configuredTags);
|
|
});
|
|
|
|
test("canvas skill tags still use defaults when no stored setting exists", () => {
|
|
const normalized = normalizeCanvasSkillTagSettings(null, {
|
|
requireCurrentVersion: true,
|
|
});
|
|
|
|
expect(normalized.tags).toEqual(DEFAULT_CANVAS_SKILL_TAGS);
|
|
expect(normalized.tags.map((tag) => tag.name)).toEqual(
|
|
expect.arrayContaining(["产品生命周期总览", "新品进度"])
|
|
);
|
|
});
|
|
|
|
test("canvas skill categories keep valid assignments and clear removed categories", () => {
|
|
const normalized = normalizeCanvasSkillTagSettings({
|
|
enabled: true,
|
|
title: "分类技能",
|
|
categories: [
|
|
{ id: "market", name: "市场分析" },
|
|
{ id: "operations", name: "运营工具" },
|
|
],
|
|
tags: [
|
|
{
|
|
id: "market-overview",
|
|
name: "市场大盘",
|
|
prompt: "查询市场大盘",
|
|
categoryId: "market",
|
|
},
|
|
{
|
|
id: "legacy-skill",
|
|
name: "旧技能",
|
|
prompt: "旧技能提示词",
|
|
categoryId: "removed-category",
|
|
},
|
|
],
|
|
version: 6,
|
|
});
|
|
|
|
expect(normalized.categories).toEqual([
|
|
{ id: "market", name: "市场分析" },
|
|
{ id: "operations", name: "运营工具" },
|
|
]);
|
|
expect(normalized.tags[0]?.categoryId).toBe("market");
|
|
expect(normalized.tags[1]?.categoryId).toBeUndefined();
|
|
});
|
|
});
|