fix(settings): restore wechat and payment config persistence

This commit is contained in:
IanShaw027
2026-04-21 17:35:12 +08:00
parent d08757ce9e
commit ee3f158f4e
19 changed files with 7160 additions and 4473 deletions

View File

@@ -0,0 +1,21 @@
import { describe, expect, it } from "vitest";
import {
defaultWeChatConnectScopesForMode,
normalizeWeChatConnectMode,
} from "@/api/admin/settings";
describe("admin settings wechat connect helpers", () => {
it("normalizes legacy or noisy mode values to the backend contract", () => {
expect(normalizeWeChatConnectMode("OPEN")).toBe("open");
expect(normalizeWeChatConnectMode(" open_platform ")).toBe("open");
expect(normalizeWeChatConnectMode("mp")).toBe("mp");
expect(normalizeWeChatConnectMode("official_account")).toBe("mp");
expect(normalizeWeChatConnectMode("unknown")).toBe("open");
});
it("maps each mode to the backend default scopes", () => {
expect(defaultWeChatConnectScopesForMode("open")).toBe("snsapi_login");
expect(defaultWeChatConnectScopesForMode("mp")).toBe("snsapi_userinfo");
});
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { defineComponent, h, ref } from 'vue'
import { flushPromises, mount } from '@vue/test-utils'
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'
import SettingsView from "../SettingsView.vue";
const {
getSettings,
@@ -38,9 +38,9 @@ const {
adminSettingsFetch: vi.fn(),
showError: vi.fn(),
showSuccess: vi.fn(),
}))
}));
vi.mock('@/api', () => ({
vi.mock("@/api", () => ({
adminAPI: {
settings: {
getSettings,
@@ -63,9 +63,9 @@ vi.mock('@/api', () => ({
getProviders,
},
},
}))
}));
vi.mock('@/stores', () => ({
vi.mock("@/stores", () => ({
useAppStore: () => ({
showError,
showSuccess,
@@ -73,36 +73,36 @@ vi.mock('@/stores', () => ({
showInfo: vi.fn(),
fetchPublicSettings,
}),
}))
}));
vi.mock('@/stores/adminSettings', () => ({
vi.mock("@/stores/adminSettings", () => ({
useAdminSettingsStore: () => ({
fetch: adminSettingsFetch,
}),
}))
}));
vi.mock('@/composables/useClipboard', () => ({
vi.mock("@/composables/useClipboard", () => ({
useClipboard: () => ({
copyToClipboard: vi.fn(),
}),
}))
}));
vi.mock('@/utils/apiError', () => ({
extractApiErrorMessage: () => 'error',
}))
vi.mock("@/utils/apiError", () => ({
extractApiErrorMessage: () => "error",
}));
vi.mock('vue-i18n', async () => {
const actual = await vi.importActual<typeof import('vue-i18n')>('vue-i18n')
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'),
locale: ref("zh-CN"),
}),
}
})
};
});
const AppLayoutStub = { template: '<div><slot /></div>' }
const AppLayoutStub = { template: "<div><slot /></div>" };
const ToggleStub = defineComponent({
props: {
modelValue: {
@@ -110,25 +110,25 @@ const ToggleStub = defineComponent({
default: false,
},
},
emits: ['update:modelValue'],
emits: ["update:modelValue"],
setup(props, { emit }) {
return () =>
h('input', {
class: 'toggle-stub',
type: 'checkbox',
h("input", {
class: "toggle-stub",
type: "checkbox",
checked: props.modelValue,
onChange: (event: Event) => {
emit('update:modelValue', (event.target as HTMLInputElement).checked)
emit("update:modelValue", (event.target as HTMLInputElement).checked);
},
})
});
},
})
});
const SelectStub = defineComponent({
props: {
modelValue: {
type: [String, Number, Boolean, null],
default: '',
default: "",
},
options: {
type: Array,
@@ -136,42 +136,43 @@ const SelectStub = defineComponent({
},
placeholder: {
type: String,
default: '',
default: "",
},
},
emits: ['update:modelValue', 'change'],
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)
}
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',
"select",
{
class: 'select-stub',
value: props.modelValue ?? '',
'data-placeholder': props.placeholder,
class: "select-stub",
value: props.modelValue ?? "",
"data-placeholder": props.placeholder,
onChange,
},
(props.options as Array<Record<string, unknown>>).map((option) =>
h(
'option',
"option",
{
key: `${String(option.value ?? '')}:${String(option.label ?? '')}`,
key: `${String(option.value ?? "")}:${String(option.label ?? "")}`,
value: option.value as string,
},
String(option.label ?? '')
)
)
)
String(option.label ?? ""),
),
),
);
},
})
});
const baseSettingsResponse = {
registration_enabled: true,
@@ -185,69 +186,77 @@ const baseSettingsResponse = {
default_balance: 0,
default_concurrency: 1,
default_subscriptions: [],
site_name: 'Sub2API',
site_logo: '',
site_subtitle: '',
api_base_url: '',
contact_info: '',
doc_url: '',
home_content: '',
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: '',
frontend_url: "",
smtp_host: "",
smtp_port: 587,
smtp_username: '',
smtp_username: "",
smtp_password_configured: false,
smtp_from_email: '',
smtp_from_name: '',
smtp_from_email: "",
smtp_from_name: "",
smtp_use_tls: true,
turnstile_enabled: false,
turnstile_site_key: '',
turnstile_site_key: "",
turnstile_secret_key_configured: false,
linuxdo_connect_enabled: false,
linuxdo_connect_client_id: '',
linuxdo_connect_client_id: "",
linuxdo_connect_client_secret_configured: false,
linuxdo_connect_redirect_url: '',
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_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_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_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: '',
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: '',
fallback_model_anthropic: "",
fallback_model_openai: "",
fallback_model_gemini: "",
fallback_model_antigravity: "",
enable_identity_patch: false,
identity_patch_prompt: '',
identity_patch_prompt: "",
ops_monitoring_enabled: false,
ops_realtime_monitoring_enabled: false,
ops_query_mode_default: 'auto',
ops_query_mode_default: "auto",
ops_metrics_interval_seconds: 60,
min_claude_code_version: '',
max_claude_code_version: '',
min_claude_code_version: "",
max_claude_code_version: "",
allow_ungrouped_key_scheduling: false,
enable_fingerprint_unification: true,
enable_metadata_passthrough: false,
@@ -262,27 +271,27 @@ const baseSettingsResponse = {
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_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_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: '',
balance_low_notify_recharge_url: "",
account_quota_notify_enabled: false,
account_quota_notify_emails: [],
}
};
function mountView() {
return mount(SettingsView, {
@@ -302,184 +311,361 @@ function mountView() {
BackupSettings: true,
},
},
})
});
}
async function openPaymentTab(wrapper: ReturnType<typeof mountView>) {
const paymentTabButton = wrapper
.findAll('button')
.find((node) => node.text().includes('admin.settings.tabs.payment'))
.findAll("button")
.find((node) => node.text().includes("admin.settings.tabs.payment"));
expect(paymentTabButton).toBeDefined()
await paymentTabButton?.trigger('click')
await flushPromises()
expect(paymentTabButton).toBeDefined();
await paymentTabButton?.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()
async function openSecurityTab(wrapper: ReturnType<typeof mountView>) {
const securityTabButton = wrapper
.findAll("button")
.find((node) => node.text().includes("admin.settings.tabs.security"));
getSettings.mockResolvedValue({ ...baseSettingsResponse })
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: '',
})
masked_key: "",
});
getOverloadCooldownSettings.mockResolvedValue({
enabled: true,
cooldown_minutes: 10,
})
});
getStreamTimeoutSettings.mockResolvedValue({
enabled: true,
action: 'temp_unsched',
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([])
});
getGroups.mockResolvedValue([]);
listProxies.mockResolvedValue({
items: [],
})
});
getProviders.mockResolvedValue({
data: [],
})
fetchPublicSettings.mockResolvedValue(undefined)
adminSettingsFetch.mockResolvedValue(undefined)
})
});
fetchPublicSettings.mockResolvedValue(undefined);
adminSettingsFetch.mockResolvedValue(undefined);
});
it('loads canonical source options and normalizes existing values', async () => {
const wrapper = mountView()
it("loads canonical source options and normalizes existing values", async () => {
const wrapper = mountView();
await flushPromises()
await openPaymentTab(wrapper)
await flushPromises();
await openPaymentTab(wrapper);
const paymentSourceSelects = wrapper
.findAll('select.select-stub')
.filter((node) => ['alipay', 'wxpay'].includes(node.attributes('data-placeholder')))
.findAll("select.select-stub")
.filter((node) =>
["alipay", "wxpay"].includes(node.attributes("data-placeholder")),
);
expect(paymentSourceSelects).toHaveLength(2)
expect(paymentSourceSelects).toHaveLength(2);
const alipaySelect = paymentSourceSelects.find(
(node) => node.attributes('data-placeholder') === 'alipay'
)
(node) => node.attributes("data-placeholder") === "alipay",
);
const wxpaySelect = paymentSourceSelects.find(
(node) => node.attributes('data-placeholder') === 'wxpay'
)
(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(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',
])
})
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()
it("saves canonical source keys selected from the dropdowns", async () => {
const wrapper = mountView();
await flushPromises()
await openPaymentTab(wrapper)
await flushPromises();
await openPaymentTab(wrapper);
const paymentSourceSelects = wrapper
.findAll('select.select-stub')
.filter((node) => ['alipay', 'wxpay'].includes(node.attributes('data-placeholder')))
.findAll("select.select-stub")
.filter((node) =>
["alipay", "wxpay"].includes(node.attributes("data-placeholder")),
);
const alipaySelect = paymentSourceSelects.find(
(node) => node.attributes('data-placeholder') === 'alipay'
)
(node) => node.attributes("data-placeholder") === "alipay",
);
const wxpaySelect = paymentSourceSelects.find(
(node) => node.attributes('data-placeholder') === 'wxpay'
)
(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()
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).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_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()
it("blocks saving when a visible payment method is enabled without a source", async () => {
const wrapper = mountView();
await flushPromises()
await openPaymentTab(wrapper)
await flushPromises();
await openPaymentTab(wrapper);
const paymentSourceSelects = wrapper
.findAll('select.select-stub')
.filter((node) => ['alipay', 'wxpay'].includes(node.attributes('data-placeholder')))
.findAll("select.select-stub")
.filter((node) =>
["alipay", "wxpay"].includes(node.attributes("data-placeholder")),
);
const alipaySelect = paymentSourceSelects.find(
(node) => node.attributes('data-placeholder') === 'alipay'
)
(node) => node.attributes("data-placeholder") === "alipay",
);
await alipaySelect?.setValue('')
await wrapper.find('form').trigger('submit.prevent')
await flushPromises()
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('支付来源')
})
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()
it("renders advanced scheduler copy as local experimental gateway policy", async () => {
const wrapper = mountView();
await flushPromises()
await flushPromises();
expect(wrapper.text()).toContain('OpenAI 实验调度策略')
expect(wrapper.text()).toContain('默认关闭。开启后仅影响本网关在 OpenAI 账号间的实验性调度选择逻辑')
expect(wrapper.text()).not.toContain('OpenAI 高级调度器')
})
})
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("密钥已配置");
});
});