fix: close admin settings review gaps
This commit is contained in:
@@ -27,8 +27,8 @@ describe('admin settings payment visible method helpers', () => {
|
|||||||
expect(getPaymentVisibleMethodSourceOptions('alipay')).toEqual([
|
expect(getPaymentVisibleMethodSourceOptions('alipay')).toEqual([
|
||||||
{
|
{
|
||||||
value: '',
|
value: '',
|
||||||
labelZh: '自动路由',
|
labelZh: '未配置',
|
||||||
labelEn: 'Automatic routing',
|
labelEn: 'Not configured',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'official_alipay',
|
value: 'official_alipay',
|
||||||
@@ -45,8 +45,8 @@ describe('admin settings payment visible method helpers', () => {
|
|||||||
expect(getPaymentVisibleMethodSourceOptions('wxpay')).toEqual([
|
expect(getPaymentVisibleMethodSourceOptions('wxpay')).toEqual([
|
||||||
{
|
{
|
||||||
value: '',
|
value: '',
|
||||||
labelZh: '自动路由',
|
labelZh: '未配置',
|
||||||
labelEn: 'Automatic routing',
|
labelEn: 'Not configured',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'official_wxpay',
|
value: 'official_wxpay',
|
||||||
|
|||||||
@@ -44,12 +44,12 @@ const PAYMENT_VISIBLE_METHOD_SOURCE_OPTIONS: Record<
|
|||||||
PaymentVisibleMethodSourceOption[]
|
PaymentVisibleMethodSourceOption[]
|
||||||
> = {
|
> = {
|
||||||
alipay: [
|
alipay: [
|
||||||
{ value: '', labelZh: '自动路由', labelEn: 'Automatic routing' },
|
{ value: '', labelZh: '未配置', labelEn: 'Not configured' },
|
||||||
{ value: 'official_alipay', labelZh: '支付宝官方', labelEn: 'Official Alipay' },
|
{ value: 'official_alipay', labelZh: '支付宝官方', labelEn: 'Official Alipay' },
|
||||||
{ value: 'easypay_alipay', labelZh: '易支付支付宝', labelEn: 'EasyPay Alipay' },
|
{ value: 'easypay_alipay', labelZh: '易支付支付宝', labelEn: 'EasyPay Alipay' },
|
||||||
],
|
],
|
||||||
wxpay: [
|
wxpay: [
|
||||||
{ value: '', labelZh: '自动路由', labelEn: 'Automatic routing' },
|
{ value: '', labelZh: '未配置', labelEn: 'Not configured' },
|
||||||
{ value: 'official_wxpay', labelZh: '微信官方', labelEn: 'Official WeChat Pay' },
|
{ value: 'official_wxpay', labelZh: '微信官方', labelEn: 'Official WeChat Pay' },
|
||||||
{ value: 'easypay_wxpay', labelZh: '易支付微信', labelEn: 'EasyPay WeChat Pay' },
|
{ value: 'easypay_wxpay', labelZh: '易支付微信', labelEn: 'EasyPay WeChat Pay' },
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -663,6 +663,12 @@ const adminNavItems = computed((): NavItem[] => {
|
|||||||
? [{ path: '/admin/ops', label: t('nav.ops'), icon: ChartIcon }]
|
? [{ path: '/admin/ops', label: t('nav.ops'), icon: ChartIcon }]
|
||||||
: []),
|
: []),
|
||||||
{ path: '/admin/users', label: t('nav.users'), icon: UsersIcon, hideInSimpleMode: true },
|
{ path: '/admin/users', label: t('nav.users'), icon: UsersIcon, hideInSimpleMode: true },
|
||||||
|
{
|
||||||
|
path: '/admin/users/auth-identity-migration-reports',
|
||||||
|
label: 'Migration Reports',
|
||||||
|
icon: UsersIcon,
|
||||||
|
hideInSimpleMode: true
|
||||||
|
},
|
||||||
{ path: '/admin/groups', label: t('nav.groups'), icon: FolderIcon, hideInSimpleMode: true },
|
{ path: '/admin/groups', label: t('nav.groups'), icon: FolderIcon, hideInSimpleMode: true },
|
||||||
{ path: '/admin/channels', label: t('nav.channels', '渠道管理'), icon: ChannelIcon, hideInSimpleMode: true },
|
{ path: '/admin/channels', label: t('nav.channels', '渠道管理'), icon: ChannelIcon, hideInSimpleMode: true },
|
||||||
{ path: '/admin/subscriptions', label: t('nav.subscriptions'), icon: CreditCardIcon, hideInSimpleMode: true },
|
{ path: '/admin/subscriptions', label: t('nav.subscriptions'), icon: CreditCardIcon, hideInSimpleMode: true },
|
||||||
|
|||||||
@@ -30,3 +30,9 @@ describe('AppSidebar header styles', () => {
|
|||||||
expect(sidebarBrandBlockMatch?.[0]).not.toContain('overflow: hidden;')
|
expect(sidebarBrandBlockMatch?.[0]).not.toContain('overflow: hidden;')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('AppSidebar admin navigation', () => {
|
||||||
|
it('includes a visible entry for auth identity migration reports', () => {
|
||||||
|
expect(componentSource).toContain("'/admin/users/auth-identity-migration-reports'")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -318,6 +318,7 @@ const pagination = reactive({
|
|||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
total: 0,
|
total: 0,
|
||||||
})
|
})
|
||||||
|
const knownReportTypes = ref<string[]>([])
|
||||||
|
|
||||||
const columns: Column[] = [
|
const columns: Column[] = [
|
||||||
{ key: 'status', label: text('状态', 'Status') },
|
{ key: 'status', label: text('状态', 'Status') },
|
||||||
@@ -330,12 +331,16 @@ const columns: Column[] = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const reportTypeOptions = computed(() =>
|
const reportTypeOptions = computed(() =>
|
||||||
Object.entries(summary.value.by_type)
|
knownReportTypes.value
|
||||||
.sort(([left], [right]) => left.localeCompare(right))
|
.slice()
|
||||||
.map(([value, count]) => ({
|
.sort((left, right) => left.localeCompare(right))
|
||||||
value,
|
.map((value) => {
|
||||||
label: `${value} (${count})`,
|
const count = summary.value.by_type[value]
|
||||||
}))
|
return {
|
||||||
|
value,
|
||||||
|
label: count === undefined ? value : `${value} (${count})`,
|
||||||
|
}
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const canResolve = computed(() =>
|
const canResolve = computed(() =>
|
||||||
@@ -347,10 +352,22 @@ const canResolve = computed(() =>
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const mergeKnownReportTypes = (...values: Array<string | null | undefined>) => {
|
||||||
|
const merged = new Set(knownReportTypes.value)
|
||||||
|
for (const value of values) {
|
||||||
|
const normalized = value?.trim()
|
||||||
|
if (normalized) {
|
||||||
|
merged.add(normalized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
knownReportTypes.value = Array.from(merged)
|
||||||
|
}
|
||||||
|
|
||||||
const loadSummary = async () => {
|
const loadSummary = async () => {
|
||||||
summaryLoading.value = true
|
summaryLoading.value = true
|
||||||
try {
|
try {
|
||||||
summary.value = await adminAPI.users.getAuthIdentityMigrationReportSummary()
|
summary.value = await adminAPI.users.getAuthIdentityMigrationReportSummary()
|
||||||
|
mergeKnownReportTypes(...Object.keys(summary.value.by_type))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load auth identity migration report summary:', error)
|
console.error('Failed to load auth identity migration report summary:', error)
|
||||||
appStore.showError(text('加载 migration reports 汇总失败', 'Failed to load migration report summary'))
|
appStore.showError(text('加载 migration reports 汇总失败', 'Failed to load migration report summary'))
|
||||||
@@ -370,6 +387,7 @@ const loadReports = async () => {
|
|||||||
|
|
||||||
reports.value = response.items
|
reports.value = response.items
|
||||||
pagination.total = response.total
|
pagination.total = response.total
|
||||||
|
mergeKnownReportTypes(filters.reportType, ...response.items.map((report) => report.report_type))
|
||||||
|
|
||||||
if (selectedReport.value) {
|
if (selectedReport.value) {
|
||||||
const refreshed = response.items.find((report) => report.id === selectedReport.value?.id) ?? null
|
const refreshed = response.items.find((report) => report.id === selectedReport.value?.id) ?? null
|
||||||
|
|||||||
@@ -2728,8 +2728,8 @@
|
|||||||
<p class="mt-1.5 text-xs text-gray-400">
|
<p class="mt-1.5 text-xs text-gray-400">
|
||||||
{{
|
{{
|
||||||
localText(
|
localText(
|
||||||
'留空表示自动路由;仅允许当前系统支持的官方或易支付来源。',
|
'启用后必须明确选择一个来源;未配置状态不会对外展示该支付方式。',
|
||||||
'Leave blank for automatic routing. Only supported official or EasyPay sources are allowed.'
|
'Choose an explicit source before enabling the method. Not configured methods are not exposed.'
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</p>
|
</p>
|
||||||
@@ -3450,6 +3450,28 @@ function setPaymentVisibleMethodSource(
|
|||||||
form.payment_visible_method_wxpay_source = normalized
|
form.payment_visible_method_wxpay_source = normalized
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validatePaymentVisibleMethodSelections(): boolean {
|
||||||
|
for (const visibleMethod of paymentVisibleMethodCards.value) {
|
||||||
|
if (!getPaymentVisibleMethodEnabled(visibleMethod.key)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getPaymentVisibleMethodSource(visibleMethod.key)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
appStore.showError(
|
||||||
|
localText(
|
||||||
|
`${visibleMethod.title} 已启用,请先选择支付来源`,
|
||||||
|
`Select a payment source before enabling ${visibleMethod.title}`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Proxies for web search emulation ProxySelector
|
// Proxies for web search emulation ProxySelector
|
||||||
const webSearchProxies = ref<Proxy[]>([])
|
const webSearchProxies = ref<Proxy[]>([])
|
||||||
|
|
||||||
@@ -3979,6 +4001,10 @@ async function saveSettings() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!validatePaymentVisibleMethodSelections()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Validate URL fields — novalidate disables browser-native checks, so we validate here
|
// Validate URL fields — novalidate disables browser-native checks, so we validate here
|
||||||
const isValidHttpUrl = (url: string): boolean => {
|
const isValidHttpUrl = (url: string): boolean => {
|
||||||
if (!url) return true
|
if (!url) return true
|
||||||
|
|||||||
@@ -240,4 +240,21 @@ describe('AuthIdentityMigrationReportsView', () => {
|
|||||||
reportType: '',
|
reportType: '',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('keeps report type filter options available from list data when summary fails', async () => {
|
||||||
|
getAuthIdentityMigrationReportSummary.mockRejectedValueOnce(new Error('summary failed'))
|
||||||
|
listAuthIdentityMigrationReports.mockResolvedValueOnce(listResponse)
|
||||||
|
|
||||||
|
const wrapper = mountView()
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
const options = wrapper
|
||||||
|
.get('[data-test="report-type-filter"]')
|
||||||
|
.findAll('option')
|
||||||
|
.map((node) => node.element.value)
|
||||||
|
|
||||||
|
expect(showError).toHaveBeenCalled()
|
||||||
|
expect(options).toContain('oidc_synthetic_email_requires_manual_recovery')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -449,4 +449,27 @@ describe('admin SettingsView payment visible method controls', () => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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('支付来源')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user