672 lines
19 KiB
TypeScript
672 lines
19 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { defineComponent, h, ref } from "vue";
|
|
import { flushPromises, mount } from "@vue/test-utils";
|
|
|
|
import SettingsView from "../SettingsView.vue";
|
|
|
|
const {
|
|
getSettings,
|
|
updateSettings,
|
|
getWebSearchEmulationConfig,
|
|
updateWebSearchEmulationConfig,
|
|
getAdminApiKey,
|
|
getOverloadCooldownSettings,
|
|
getStreamTimeoutSettings,
|
|
getRectifierSettings,
|
|
getBetaPolicySettings,
|
|
getGroups,
|
|
listProxies,
|
|
getProviders,
|
|
fetchPublicSettings,
|
|
adminSettingsFetch,
|
|
showError,
|
|
showSuccess,
|
|
} = vi.hoisted(() => ({
|
|
getSettings: vi.fn(),
|
|
updateSettings: vi.fn(),
|
|
getWebSearchEmulationConfig: vi.fn(),
|
|
updateWebSearchEmulationConfig: vi.fn(),
|
|
getAdminApiKey: vi.fn(),
|
|
getOverloadCooldownSettings: vi.fn(),
|
|
getStreamTimeoutSettings: vi.fn(),
|
|
getRectifierSettings: vi.fn(),
|
|
getBetaPolicySettings: vi.fn(),
|
|
getGroups: vi.fn(),
|
|
listProxies: vi.fn(),
|
|
getProviders: vi.fn(),
|
|
fetchPublicSettings: vi.fn(),
|
|
adminSettingsFetch: vi.fn(),
|
|
showError: vi.fn(),
|
|
showSuccess: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@/api", () => ({
|
|
adminAPI: {
|
|
settings: {
|
|
getSettings,
|
|
updateSettings,
|
|
getWebSearchEmulationConfig,
|
|
updateWebSearchEmulationConfig,
|
|
getAdminApiKey,
|
|
getOverloadCooldownSettings,
|
|
getStreamTimeoutSettings,
|
|
getRectifierSettings,
|
|
getBetaPolicySettings,
|
|
},
|
|
groups: {
|
|
getAll: getGroups,
|
|
},
|
|
proxies: {
|
|
list: listProxies,
|
|
},
|
|
payment: {
|
|
getProviders,
|
|
},
|
|
},
|
|
}));
|
|
|
|
vi.mock("@/stores", () => ({
|
|
useAppStore: () => ({
|
|
showError,
|
|
showSuccess,
|
|
showWarning: vi.fn(),
|
|
showInfo: vi.fn(),
|
|
fetchPublicSettings,
|
|
}),
|
|
}));
|
|
|
|
vi.mock("@/stores/adminSettings", () => ({
|
|
useAdminSettingsStore: () => ({
|
|
fetch: adminSettingsFetch,
|
|
}),
|
|
}));
|
|
|
|
vi.mock("@/composables/useClipboard", () => ({
|
|
useClipboard: () => ({
|
|
copyToClipboard: vi.fn(),
|
|
}),
|
|
}));
|
|
|
|
vi.mock("@/utils/apiError", () => ({
|
|
extractApiErrorMessage: () => "error",
|
|
}));
|
|
|
|
vi.mock("vue-i18n", async () => {
|
|
const actual = await vi.importActual<typeof import("vue-i18n")>("vue-i18n");
|
|
return {
|
|
...actual,
|
|
useI18n: () => ({
|
|
t: (key: string) => key,
|
|
locale: ref("zh-CN"),
|
|
}),
|
|
};
|
|
});
|
|
|
|
const AppLayoutStub = { template: "<div><slot /></div>" };
|
|
const ToggleStub = defineComponent({
|
|
props: {
|
|
modelValue: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
},
|
|
emits: ["update:modelValue"],
|
|
setup(props, { emit }) {
|
|
return () =>
|
|
h("input", {
|
|
class: "toggle-stub",
|
|
type: "checkbox",
|
|
checked: props.modelValue,
|
|
onChange: (event: Event) => {
|
|
emit("update:modelValue", (event.target as HTMLInputElement).checked);
|
|
},
|
|
});
|
|
},
|
|
});
|
|
|
|
const SelectStub = defineComponent({
|
|
props: {
|
|
modelValue: {
|
|
type: [String, Number, Boolean, null],
|
|
default: "",
|
|
},
|
|
options: {
|
|
type: Array,
|
|
default: () => [],
|
|
},
|
|
placeholder: {
|
|
type: String,
|
|
default: "",
|
|
},
|
|
},
|
|
emits: ["update:modelValue", "change"],
|
|
setup(props, { emit }) {
|
|
const onChange = (event: Event) => {
|
|
const target = event.target as HTMLSelectElement;
|
|
emit("update:modelValue", target.value);
|
|
const option =
|
|
(props.options as Array<Record<string, unknown>>).find(
|
|
(item) => String(item.value ?? "") === target.value,
|
|
) ?? null;
|
|
emit("change", target.value, option);
|
|
};
|
|
|
|
return () =>
|
|
h(
|
|
"select",
|
|
{
|
|
class: "select-stub",
|
|
value: props.modelValue ?? "",
|
|
"data-placeholder": props.placeholder,
|
|
onChange,
|
|
},
|
|
(props.options as Array<Record<string, unknown>>).map((option) =>
|
|
h(
|
|
"option",
|
|
{
|
|
key: `${String(option.value ?? "")}:${String(option.label ?? "")}`,
|
|
value: option.value as string,
|
|
},
|
|
String(option.label ?? ""),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
});
|
|
|
|
const baseSettingsResponse = {
|
|
registration_enabled: true,
|
|
email_verify_enabled: false,
|
|
registration_email_suffix_whitelist: [],
|
|
promo_code_enabled: true,
|
|
invitation_code_enabled: false,
|
|
password_reset_enabled: false,
|
|
totp_enabled: false,
|
|
totp_encryption_key_configured: false,
|
|
default_balance: 0,
|
|
default_concurrency: 1,
|
|
default_subscriptions: [],
|
|
site_name: "Sub2API",
|
|
site_logo: "",
|
|
site_subtitle: "",
|
|
api_base_url: "",
|
|
contact_info: "",
|
|
doc_url: "",
|
|
home_content: "",
|
|
hide_ccs_import_button: false,
|
|
table_default_page_size: 20,
|
|
table_page_size_options: [10, 20, 50, 100],
|
|
backend_mode_enabled: false,
|
|
custom_menu_items: [],
|
|
custom_endpoints: [],
|
|
frontend_url: "",
|
|
smtp_host: "",
|
|
smtp_port: 587,
|
|
smtp_username: "",
|
|
smtp_password_configured: false,
|
|
smtp_from_email: "",
|
|
smtp_from_name: "",
|
|
smtp_use_tls: true,
|
|
turnstile_enabled: false,
|
|
turnstile_site_key: "",
|
|
turnstile_secret_key_configured: false,
|
|
linuxdo_connect_enabled: false,
|
|
linuxdo_connect_client_id: "",
|
|
linuxdo_connect_client_secret_configured: false,
|
|
linuxdo_connect_redirect_url: "",
|
|
wechat_connect_enabled: true,
|
|
wechat_connect_app_id: "wx-app-id-123",
|
|
wechat_connect_app_secret_configured: true,
|
|
wechat_connect_mode: "mp",
|
|
wechat_connect_scopes: "",
|
|
wechat_connect_redirect_url:
|
|
"https://admin.example.com/api/v1/auth/oauth/wechat/callback",
|
|
wechat_connect_frontend_redirect_url: "/auth/wechat/callback",
|
|
oidc_connect_enabled: false,
|
|
oidc_connect_provider_name: "OIDC",
|
|
oidc_connect_client_id: "",
|
|
oidc_connect_client_secret_configured: false,
|
|
oidc_connect_issuer_url: "",
|
|
oidc_connect_discovery_url: "",
|
|
oidc_connect_authorize_url: "",
|
|
oidc_connect_token_url: "",
|
|
oidc_connect_userinfo_url: "",
|
|
oidc_connect_jwks_url: "",
|
|
oidc_connect_scopes: "openid email profile",
|
|
oidc_connect_redirect_url: "",
|
|
oidc_connect_frontend_redirect_url: "/auth/oidc/callback",
|
|
oidc_connect_token_auth_method: "client_secret_post",
|
|
oidc_connect_use_pkce: true,
|
|
oidc_connect_validate_id_token: true,
|
|
oidc_connect_allowed_signing_algs: "RS256,ES256,PS256",
|
|
oidc_connect_clock_skew_seconds: 120,
|
|
oidc_connect_require_email_verified: false,
|
|
oidc_connect_userinfo_email_path: "",
|
|
oidc_connect_userinfo_id_path: "",
|
|
oidc_connect_userinfo_username_path: "",
|
|
enable_model_fallback: false,
|
|
fallback_model_anthropic: "",
|
|
fallback_model_openai: "",
|
|
fallback_model_gemini: "",
|
|
fallback_model_antigravity: "",
|
|
enable_identity_patch: false,
|
|
identity_patch_prompt: "",
|
|
ops_monitoring_enabled: false,
|
|
ops_realtime_monitoring_enabled: false,
|
|
ops_query_mode_default: "auto",
|
|
ops_metrics_interval_seconds: 60,
|
|
min_claude_code_version: "",
|
|
max_claude_code_version: "",
|
|
allow_ungrouped_key_scheduling: false,
|
|
enable_fingerprint_unification: true,
|
|
enable_metadata_passthrough: false,
|
|
enable_cch_signing: false,
|
|
payment_enabled: true,
|
|
payment_min_amount: 1,
|
|
payment_max_amount: 10000,
|
|
payment_daily_limit: 50000,
|
|
payment_order_timeout_minutes: 30,
|
|
payment_max_pending_orders: 3,
|
|
payment_enabled_types: [],
|
|
payment_balance_disabled: false,
|
|
payment_balance_recharge_multiplier: 1,
|
|
payment_recharge_fee_rate: 0,
|
|
payment_load_balance_strategy: "round-robin",
|
|
payment_product_name_prefix: "",
|
|
payment_product_name_suffix: "",
|
|
payment_help_image_url: "",
|
|
payment_help_text: "",
|
|
payment_cancel_rate_limit_enabled: false,
|
|
payment_cancel_rate_limit_max: 10,
|
|
payment_cancel_rate_limit_window: 1,
|
|
payment_cancel_rate_limit_unit: "day",
|
|
payment_cancel_rate_limit_window_mode: "rolling",
|
|
payment_visible_method_alipay_source: "alipay_direct",
|
|
payment_visible_method_wxpay_source: "invalid-source",
|
|
payment_visible_method_alipay_enabled: true,
|
|
payment_visible_method_wxpay_enabled: true,
|
|
openai_advanced_scheduler_enabled: false,
|
|
balance_low_notify_enabled: false,
|
|
balance_low_notify_threshold: 0,
|
|
balance_low_notify_recharge_url: "",
|
|
account_quota_notify_enabled: false,
|
|
account_quota_notify_emails: [],
|
|
};
|
|
|
|
function mountView() {
|
|
return mount(SettingsView, {
|
|
global: {
|
|
stubs: {
|
|
AppLayout: AppLayoutStub,
|
|
Select: SelectStub,
|
|
Toggle: ToggleStub,
|
|
Icon: true,
|
|
ConfirmDialog: true,
|
|
PaymentProviderList: true,
|
|
PaymentProviderDialog: true,
|
|
GroupBadge: true,
|
|
GroupOptionItem: true,
|
|
ProxySelector: true,
|
|
ImageUpload: true,
|
|
BackupSettings: true,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
async function openPaymentTab(wrapper: ReturnType<typeof mountView>) {
|
|
const paymentTabButton = wrapper
|
|
.findAll("button")
|
|
.find((node) => node.text().includes("admin.settings.tabs.payment"));
|
|
|
|
expect(paymentTabButton).toBeDefined();
|
|
await paymentTabButton?.trigger("click");
|
|
await flushPromises();
|
|
}
|
|
|
|
async function openSecurityTab(wrapper: ReturnType<typeof mountView>) {
|
|
const securityTabButton = wrapper
|
|
.findAll("button")
|
|
.find((node) => node.text().includes("admin.settings.tabs.security"));
|
|
|
|
expect(securityTabButton).toBeDefined();
|
|
await securityTabButton?.trigger("click");
|
|
await flushPromises();
|
|
}
|
|
|
|
describe("admin SettingsView payment visible method controls", () => {
|
|
beforeEach(() => {
|
|
getSettings.mockReset();
|
|
updateSettings.mockReset();
|
|
getWebSearchEmulationConfig.mockReset();
|
|
updateWebSearchEmulationConfig.mockReset();
|
|
getAdminApiKey.mockReset();
|
|
getOverloadCooldownSettings.mockReset();
|
|
getStreamTimeoutSettings.mockReset();
|
|
getRectifierSettings.mockReset();
|
|
getBetaPolicySettings.mockReset();
|
|
getGroups.mockReset();
|
|
listProxies.mockReset();
|
|
getProviders.mockReset();
|
|
fetchPublicSettings.mockReset();
|
|
adminSettingsFetch.mockReset();
|
|
showError.mockReset();
|
|
showSuccess.mockReset();
|
|
|
|
getSettings.mockResolvedValue({ ...baseSettingsResponse });
|
|
updateSettings.mockImplementation(async (payload) => ({
|
|
...baseSettingsResponse,
|
|
...payload,
|
|
}));
|
|
getWebSearchEmulationConfig.mockResolvedValue({
|
|
enabled: false,
|
|
providers: [],
|
|
});
|
|
updateWebSearchEmulationConfig.mockResolvedValue({
|
|
enabled: false,
|
|
providers: [],
|
|
});
|
|
getAdminApiKey.mockResolvedValue({
|
|
exists: false,
|
|
masked_key: "",
|
|
});
|
|
getOverloadCooldownSettings.mockResolvedValue({
|
|
enabled: true,
|
|
cooldown_minutes: 10,
|
|
});
|
|
getStreamTimeoutSettings.mockResolvedValue({
|
|
enabled: true,
|
|
action: "temp_unsched",
|
|
temp_unsched_minutes: 5,
|
|
threshold_count: 3,
|
|
threshold_window_minutes: 10,
|
|
});
|
|
getRectifierSettings.mockResolvedValue({
|
|
enabled: true,
|
|
thinking_signature_enabled: true,
|
|
thinking_budget_enabled: true,
|
|
apikey_signature_enabled: false,
|
|
apikey_signature_patterns: [],
|
|
});
|
|
getBetaPolicySettings.mockResolvedValue({
|
|
rules: [],
|
|
});
|
|
getGroups.mockResolvedValue([]);
|
|
listProxies.mockResolvedValue({
|
|
items: [],
|
|
});
|
|
getProviders.mockResolvedValue({
|
|
data: [],
|
|
});
|
|
fetchPublicSettings.mockResolvedValue(undefined);
|
|
adminSettingsFetch.mockResolvedValue(undefined);
|
|
});
|
|
|
|
it("loads canonical source options and normalizes existing values", async () => {
|
|
const wrapper = mountView();
|
|
|
|
await flushPromises();
|
|
await openPaymentTab(wrapper);
|
|
|
|
const paymentSourceSelects = wrapper
|
|
.findAll("select.select-stub")
|
|
.filter((node) =>
|
|
["alipay", "wxpay"].includes(node.attributes("data-placeholder")),
|
|
);
|
|
|
|
expect(paymentSourceSelects).toHaveLength(2);
|
|
|
|
const alipaySelect = paymentSourceSelects.find(
|
|
(node) => node.attributes("data-placeholder") === "alipay",
|
|
);
|
|
const wxpaySelect = paymentSourceSelects.find(
|
|
(node) => node.attributes("data-placeholder") === "wxpay",
|
|
);
|
|
|
|
expect(alipaySelect?.element.value).toBe("official_alipay");
|
|
expect(
|
|
alipaySelect?.findAll("option").map((option) => option.element.value),
|
|
).toEqual(["", "official_alipay", "easypay_alipay"]);
|
|
|
|
expect(wxpaySelect?.element.value).toBe("");
|
|
expect(
|
|
wxpaySelect?.findAll("option").map((option) => option.element.value),
|
|
).toEqual(["", "official_wxpay", "easypay_wxpay"]);
|
|
});
|
|
|
|
it("saves canonical source keys selected from the dropdowns", async () => {
|
|
const wrapper = mountView();
|
|
|
|
await flushPromises();
|
|
await openPaymentTab(wrapper);
|
|
|
|
const paymentSourceSelects = wrapper
|
|
.findAll("select.select-stub")
|
|
.filter((node) =>
|
|
["alipay", "wxpay"].includes(node.attributes("data-placeholder")),
|
|
);
|
|
|
|
const alipaySelect = paymentSourceSelects.find(
|
|
(node) => node.attributes("data-placeholder") === "alipay",
|
|
);
|
|
const wxpaySelect = paymentSourceSelects.find(
|
|
(node) => node.attributes("data-placeholder") === "wxpay",
|
|
);
|
|
|
|
await alipaySelect?.setValue("easypay_alipay");
|
|
await wxpaySelect?.setValue("official_wxpay");
|
|
await wrapper.find("form").trigger("submit.prevent");
|
|
await flushPromises();
|
|
|
|
expect(updateSettings).toHaveBeenCalledTimes(1);
|
|
expect(updateSettings).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
payment_visible_method_alipay_source: "easypay_alipay",
|
|
payment_visible_method_wxpay_source: "official_wxpay",
|
|
payment_visible_method_alipay_enabled: true,
|
|
payment_visible_method_wxpay_enabled: true,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("blocks saving when a visible payment method is enabled without a source", async () => {
|
|
const wrapper = mountView();
|
|
|
|
await flushPromises();
|
|
await openPaymentTab(wrapper);
|
|
|
|
const paymentSourceSelects = wrapper
|
|
.findAll("select.select-stub")
|
|
.filter((node) =>
|
|
["alipay", "wxpay"].includes(node.attributes("data-placeholder")),
|
|
);
|
|
|
|
const alipaySelect = paymentSourceSelects.find(
|
|
(node) => node.attributes("data-placeholder") === "alipay",
|
|
);
|
|
|
|
await alipaySelect?.setValue("");
|
|
await wrapper.find("form").trigger("submit.prevent");
|
|
await flushPromises();
|
|
|
|
expect(updateSettings).not.toHaveBeenCalled();
|
|
expect(showError).toHaveBeenCalled();
|
|
expect(String(showError.mock.calls.at(-1)?.[0] ?? "")).toContain(
|
|
"支付来源",
|
|
);
|
|
});
|
|
|
|
it("renders advanced scheduler copy as local experimental gateway policy", async () => {
|
|
const wrapper = mountView();
|
|
|
|
await flushPromises();
|
|
|
|
expect(wrapper.text()).toContain("OpenAI 实验调度策略");
|
|
expect(wrapper.text()).toContain(
|
|
"默认关闭。开启后仅影响本网关在 OpenAI 账号间的实验性调度选择逻辑",
|
|
);
|
|
expect(wrapper.text()).not.toContain("OpenAI 高级调度器");
|
|
});
|
|
});
|
|
|
|
describe("admin SettingsView wechat connect controls", () => {
|
|
beforeEach(() => {
|
|
getSettings.mockReset();
|
|
updateSettings.mockReset();
|
|
getWebSearchEmulationConfig.mockReset();
|
|
updateWebSearchEmulationConfig.mockReset();
|
|
getAdminApiKey.mockReset();
|
|
getOverloadCooldownSettings.mockReset();
|
|
getStreamTimeoutSettings.mockReset();
|
|
getRectifierSettings.mockReset();
|
|
getBetaPolicySettings.mockReset();
|
|
getGroups.mockReset();
|
|
listProxies.mockReset();
|
|
getProviders.mockReset();
|
|
fetchPublicSettings.mockReset();
|
|
adminSettingsFetch.mockReset();
|
|
showError.mockReset();
|
|
showSuccess.mockReset();
|
|
|
|
getSettings.mockResolvedValue({
|
|
...baseSettingsResponse,
|
|
payment_visible_method_wxpay_source: "official_wxpay",
|
|
});
|
|
updateSettings.mockImplementation(async (payload) => ({
|
|
...baseSettingsResponse,
|
|
payment_visible_method_wxpay_source: "official_wxpay",
|
|
...payload,
|
|
}));
|
|
getWebSearchEmulationConfig.mockResolvedValue({
|
|
enabled: false,
|
|
providers: [],
|
|
});
|
|
updateWebSearchEmulationConfig.mockResolvedValue({
|
|
enabled: false,
|
|
providers: [],
|
|
});
|
|
getAdminApiKey.mockResolvedValue({
|
|
exists: false,
|
|
masked_key: "",
|
|
});
|
|
getOverloadCooldownSettings.mockResolvedValue({
|
|
enabled: true,
|
|
cooldown_minutes: 10,
|
|
});
|
|
getStreamTimeoutSettings.mockResolvedValue({
|
|
enabled: true,
|
|
action: "temp_unsched",
|
|
temp_unsched_minutes: 5,
|
|
threshold_count: 3,
|
|
threshold_window_minutes: 10,
|
|
});
|
|
getRectifierSettings.mockResolvedValue({
|
|
enabled: true,
|
|
thinking_signature_enabled: true,
|
|
thinking_budget_enabled: true,
|
|
apikey_signature_enabled: false,
|
|
apikey_signature_patterns: [],
|
|
});
|
|
getBetaPolicySettings.mockResolvedValue({
|
|
rules: [],
|
|
});
|
|
getGroups.mockResolvedValue([]);
|
|
listProxies.mockResolvedValue({
|
|
items: [],
|
|
});
|
|
getProviders.mockResolvedValue({
|
|
data: [],
|
|
});
|
|
fetchPublicSettings.mockResolvedValue(undefined);
|
|
adminSettingsFetch.mockResolvedValue(undefined);
|
|
});
|
|
|
|
it("loads and echoes WeChat Connect fields from the backend payload", async () => {
|
|
const wrapper = mountView();
|
|
|
|
await flushPromises();
|
|
await openSecurityTab(wrapper);
|
|
|
|
expect(
|
|
(
|
|
wrapper.get('[data-testid="wechat-connect-app-id"]')
|
|
.element as HTMLInputElement
|
|
).value,
|
|
).toBe("wx-app-id-123");
|
|
expect(
|
|
(
|
|
wrapper.get('[data-testid="wechat-connect-mode"]')
|
|
.element as HTMLSelectElement
|
|
).value,
|
|
).toBe("mp");
|
|
expect(
|
|
(
|
|
wrapper.get('[data-testid="wechat-connect-scopes"]')
|
|
.element as HTMLInputElement
|
|
).value,
|
|
).toBe("snsapi_userinfo");
|
|
expect(
|
|
wrapper
|
|
.get('[data-testid="wechat-connect-app-secret"]')
|
|
.attributes("placeholder"),
|
|
).toContain("密钥已配置");
|
|
expect(
|
|
(
|
|
wrapper.get('[data-testid="wechat-connect-frontend-redirect-url"]')
|
|
.element as HTMLInputElement
|
|
).value,
|
|
).toBe("/auth/wechat/callback");
|
|
});
|
|
|
|
it("saves WeChat Connect fields using the backend contract and clears the secret after save", async () => {
|
|
const wrapper = mountView();
|
|
|
|
await flushPromises();
|
|
await openSecurityTab(wrapper);
|
|
|
|
await wrapper
|
|
.get('[data-testid="wechat-connect-app-id"]')
|
|
.setValue("wx-app-id-updated");
|
|
await wrapper
|
|
.get('[data-testid="wechat-connect-app-secret"]')
|
|
.setValue("new-secret");
|
|
await wrapper.get('[data-testid="wechat-connect-mode"]').setValue("open");
|
|
await wrapper
|
|
.get('[data-testid="wechat-connect-scopes"]')
|
|
.setValue(" snsapi_base ");
|
|
await wrapper
|
|
.get('[data-testid="wechat-connect-redirect-url"]')
|
|
.setValue("https://admin.example.com/api/v1/auth/oauth/wechat/callback");
|
|
await wrapper
|
|
.get('[data-testid="wechat-connect-frontend-redirect-url"]')
|
|
.setValue("/auth/wechat/callback");
|
|
await wrapper.find("form").trigger("submit.prevent");
|
|
await flushPromises();
|
|
|
|
expect(updateSettings).toHaveBeenCalledTimes(1);
|
|
expect(updateSettings).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
wechat_connect_enabled: true,
|
|
wechat_connect_app_id: "wx-app-id-updated",
|
|
wechat_connect_app_secret: "new-secret",
|
|
wechat_connect_mode: "open",
|
|
wechat_connect_scopes: "snsapi_base",
|
|
wechat_connect_redirect_url:
|
|
"https://admin.example.com/api/v1/auth/oauth/wechat/callback",
|
|
wechat_connect_frontend_redirect_url: "/auth/wechat/callback",
|
|
}),
|
|
);
|
|
expect(
|
|
(
|
|
wrapper.get('[data-testid="wechat-connect-app-secret"]')
|
|
.element as HTMLInputElement
|
|
).value,
|
|
).toBe("");
|
|
expect(
|
|
wrapper
|
|
.get('[data-testid="wechat-connect-app-secret"]')
|
|
.attributes("placeholder"),
|
|
).toContain("密钥已配置");
|
|
});
|
|
});
|