Commit d08757ce authored by IanShaw027's avatar IanShaw027
Browse files

refactor(admin): remove auth migration reports

parent c624cce8
...@@ -1438,8 +1438,8 @@ export default { ...@@ -1438,8 +1438,8 @@ export default {
usage: 'Usage', usage: 'Usage',
concurrency: 'Concurrency', concurrency: 'Concurrency',
status: 'Status', status: 'Status',
lastLogin: 'Last Login',
lastActive: 'Last Active', lastActive: 'Last Active',
lastUsed: 'Last Used',
created: 'Created', created: 'Created',
actions: 'Actions' actions: 'Actions'
}, },
......
...@@ -1464,8 +1464,8 @@ export default { ...@@ -1464,8 +1464,8 @@ export default {
usage: '用量', usage: '用量',
concurrency: '并发数', concurrency: '并发数',
status: '状态', status: '状态',
lastLogin: '最后登录', lastActive: '最后活跃时间',
lastActive: '最后使用', lastUsed: '最后使用时间',
created: '创建时间', created: '创建时间',
actions: '操作' actions: '操作'
}, },
......
...@@ -341,16 +341,6 @@ const routes: RouteRecordRaw[] = [ ...@@ -341,16 +341,6 @@ const routes: RouteRecordRaw[] = [
descriptionKey: 'admin.users.description' descriptionKey: 'admin.users.description'
} }
}, },
{
path: '/admin/users/auth-identity-migration-reports',
name: 'AdminAuthIdentityMigrationReports',
component: () => import('@/views/admin/AuthIdentityMigrationReportsView.vue'),
meta: {
requiresAuth: true,
requiresAdmin: true,
title: 'Auth Identity Migration Reports'
}
},
{ {
path: '/admin/groups', path: '/admin/groups',
name: 'AdminGroups', name: 'AdminGroups',
......
...@@ -84,7 +84,6 @@ export interface User { ...@@ -84,7 +84,6 @@ export interface User {
balance_notify_threshold: number | null balance_notify_threshold: number | null
balance_notify_extra_emails: NotifyEmailEntry[] balance_notify_extra_emails: NotifyEmailEntry[]
subscriptions?: UserSubscription[] // User's active subscriptions subscriptions?: UserSubscription[] // User's active subscriptions
last_login_at?: string | null
last_active_at?: string | null last_active_at?: string | null
created_at: string created_at: string
updated_at: string updated_at: string
......
...@@ -712,8 +712,8 @@ const allColumns = computed<Column[]>(() => [ ...@@ -712,8 +712,8 @@ const allColumns = computed<Column[]>(() => [
{ key: 'usage', label: t('admin.users.columns.usage'), sortable: false }, { key: 'usage', label: t('admin.users.columns.usage'), sortable: false },
{ key: 'concurrency', label: t('admin.users.columns.concurrency'), sortable: true }, { key: 'concurrency', label: t('admin.users.columns.concurrency'), sortable: true },
{ key: 'status', label: t('admin.users.columns.status'), sortable: true }, { key: 'status', label: t('admin.users.columns.status'), sortable: true },
{ key: 'last_used_at', label: t('admin.users.columns.lastUsed'), sortable: true },
{ key: 'last_active_at', label: t('admin.users.columns.lastActive'), sortable: true }, { key: 'last_active_at', label: t('admin.users.columns.lastActive'), sortable: true },
{ key: 'last_used_at', label: t('admin.users.columns.lastUsed'), sortable: true },
{ key: 'created_at', label: t('admin.users.columns.created'), sortable: true }, { key: 'created_at', label: t('admin.users.columns.created'), sortable: true },
{ key: 'actions', label: t('admin.users.columns.actions'), sortable: false } { key: 'actions', label: t('admin.users.columns.actions'), sortable: false }
]) ])
......
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { flushPromises, mount } from '@vue/test-utils'
import { defineComponent, h } from 'vue'
import AuthIdentityMigrationReportsView from '../AuthIdentityMigrationReportsView.vue'
const {
bindUserAuthIdentity,
getAuthIdentityMigrationReportSummary,
listAuthIdentityMigrationReports,
resolveAuthIdentityMigrationReport,
} = vi.hoisted(() => ({
bindUserAuthIdentity: vi.fn(),
getAuthIdentityMigrationReportSummary: vi.fn(),
listAuthIdentityMigrationReports: vi.fn(),
resolveAuthIdentityMigrationReport: vi.fn(),
}))
const { showError, showSuccess } = vi.hoisted(() => ({
showError: vi.fn(),
showSuccess: vi.fn(),
}))
vi.mock('@/api/admin', () => ({
adminAPI: {
users: {
bindUserAuthIdentity,
getAuthIdentityMigrationReportSummary,
listAuthIdentityMigrationReports,
resolveAuthIdentityMigrationReport,
},
},
}))
vi.mock('@/stores/app', () => ({
useAppStore: () => ({
showError,
showSuccess,
}),
}))
vi.mock('vue-i18n', async () => {
const actual = await vi.importActual<typeof import('vue-i18n')>('vue-i18n')
return {
...actual,
useI18n: () => ({
locale: { value: 'en' },
t: (key: string) => key,
}),
}
})
vi.mock('@/utils/format', () => ({
formatDateTime: (value: string | null | undefined) => value ?? '',
}))
const sampleReport = {
id: 1,
report_type: 'oidc_synthetic_email_requires_manual_recovery',
report_key: 'legacy@example.invalid',
details: {
user_id: 42,
legacy_email: 'legacy@example.invalid',
provider_key: 'https://issuer.example',
provider_subject: 'subject-123',
},
created_at: '2026-04-20T01:02:03Z',
resolved_at: null,
resolved_by_user_id: null,
resolution_note: '',
}
const summaryResponse = {
total: 2,
open_total: 1,
resolved_total: 1,
by_type: {
oidc_synthetic_email_requires_manual_recovery: 2,
},
}
const listResponse = {
items: [sampleReport],
total: 1,
page: 1,
page_size: 20,
pages: 1,
}
const AppLayoutStub = defineComponent({
setup(_, { slots }) {
return () => h('div', slots.default?.())
},
})
const TablePageLayoutStub = defineComponent({
setup(_, { slots }) {
return () => h('div', [
slots.actions?.(),
slots.filters?.(),
slots.table?.(),
slots.default?.(),
slots.pagination?.(),
])
},
})
const DataTableStub = defineComponent({
props: {
columns: { type: Array, default: () => [] },
data: { type: Array, default: () => [] },
loading: { type: Boolean, default: false },
},
setup(props, { slots }) {
return () => h('div', { 'data-test': 'data-table' }, [
props.loading
? h('div', 'loading')
: (props.data as Array<Record<string, unknown>>).map((row) =>
h(
'div',
{ key: String(row.id ?? row.report_key) },
(props.columns as Array<{ key: string }>).map((column) => {
const slot = slots[`cell-${column.key}`]
return h(
'div',
{ key: column.key, [`data-test-cell`]: `${String(row.id)}-${column.key}` },
slot
? slot({ row, value: row[column.key] })
: String(row[column.key] ?? '')
)
})
)
),
])
},
})
const PaginationStub = defineComponent({
props: {
total: { type: Number, required: true },
page: { type: Number, required: true },
pageSize: { type: Number, required: true },
},
emits: ['update:page', 'update:pageSize'],
setup(props, { emit }) {
return () => h('div', { 'data-test': 'pagination' }, [
h('button', {
type: 'button',
'data-test': 'next-page',
onClick: () => emit('update:page', props.page + 1),
}, 'next'),
h('button', {
type: 'button',
'data-test': 'page-size-50',
onClick: () => emit('update:pageSize', 50),
}, '50'),
])
},
})
describe('AuthIdentityMigrationReportsView', () => {
beforeEach(() => {
getAuthIdentityMigrationReportSummary.mockReset()
listAuthIdentityMigrationReports.mockReset()
resolveAuthIdentityMigrationReport.mockReset()
bindUserAuthIdentity.mockReset()
showError.mockReset()
showSuccess.mockReset()
getAuthIdentityMigrationReportSummary.mockResolvedValue(summaryResponse)
listAuthIdentityMigrationReports.mockResolvedValue(listResponse)
resolveAuthIdentityMigrationReport.mockResolvedValue({
...sampleReport,
resolved_at: '2026-04-20T02:00:00Z',
resolved_by_user_id: 100,
resolution_note: 'resolved by admin',
})
bindUserAuthIdentity.mockResolvedValue({
identity_id: 77,
provider_type: 'oidc',
provider_key: 'https://issuer.example',
provider_subject: 'subject-123',
})
})
const mountView = () =>
mount(AuthIdentityMigrationReportsView, {
global: {
stubs: {
AppLayout: AppLayoutStub,
TablePageLayout: TablePageLayoutStub,
DataTable: DataTableStub,
Pagination: PaginationStub,
Icon: true,
},
},
})
it('loads summary and first page of reports on mount', async () => {
const wrapper = mountView()
await flushPromises()
expect(getAuthIdentityMigrationReportSummary).toHaveBeenCalledTimes(1)
expect(listAuthIdentityMigrationReports).toHaveBeenCalledWith({
page: 1,
pageSize: 20,
reportType: '',
})
expect(wrapper.get('[data-test="summary-total"]').text()).toContain('2')
expect(wrapper.get('[data-test="summary-open"]').text()).toContain('1')
expect(wrapper.get('[data-test="summary-resolved"]').text()).toContain('1')
expect(wrapper.text()).toContain('legacy@example.invalid')
})
it('reloads list when the report type filter changes', async () => {
const wrapper = mountView()
await flushPromises()
listAuthIdentityMigrationReports.mockClear()
await wrapper.get('[data-test="report-type-filter"]').setValue(
'oidc_synthetic_email_requires_manual_recovery'
)
await flushPromises()
expect(listAuthIdentityMigrationReports).toHaveBeenCalledWith({
page: 1,
pageSize: 20,
reportType: 'oidc_synthetic_email_requires_manual_recovery',
})
})
it('submits resolve note for the selected report and refreshes data', async () => {
const wrapper = mountView()
await flushPromises()
getAuthIdentityMigrationReportSummary.mockClear()
listAuthIdentityMigrationReports.mockClear()
await wrapper.get('[data-test="select-report-1"]').trigger('click')
await wrapper.get('[data-test="resolution-note"]').setValue('resolved by admin')
await wrapper.get('[data-test="resolve-submit"]').trigger('click')
await flushPromises()
expect(resolveAuthIdentityMigrationReport).toHaveBeenCalledWith(1, 'resolved by admin')
expect(showSuccess).toHaveBeenCalled()
expect(getAuthIdentityMigrationReportSummary).toHaveBeenCalledTimes(1)
expect(listAuthIdentityMigrationReports).toHaveBeenCalledWith({
page: 1,
pageSize: 20,
reportType: '',
})
})
it('pre-fills and submits remediation binding for the selected report', async () => {
const wrapper = mountView()
await flushPromises()
await wrapper.get('[data-test="select-report-1"]').trigger('click')
await flushPromises()
expect((wrapper.get('[data-test="remediation-user-id"]').element as HTMLInputElement).value).toBe('42')
expect((wrapper.get('[data-test="remediation-provider-type"]').element as HTMLInputElement).value).toBe('oidc')
expect((wrapper.get('[data-test="remediation-provider-key"]').element as HTMLInputElement).value).toBe(
'https://issuer.example'
)
expect((wrapper.get('[data-test="remediation-provider-subject"]').element as HTMLInputElement).value).toBe(
'subject-123'
)
await wrapper.get('[data-test="remediation-submit"]').trigger('click')
await flushPromises()
expect(bindUserAuthIdentity).toHaveBeenCalledWith(42, {
provider_type: 'oidc',
provider_key: 'https://issuer.example',
provider_subject: 'subject-123',
issuer: undefined,
metadata: {},
})
expect(showSuccess).toHaveBeenCalled()
})
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')
})
})
...@@ -70,7 +70,6 @@ const createAdminUser = (): AdminUser => ({ ...@@ -70,7 +70,6 @@ const createAdminUser = (): AdminUser => ({
created_at: '2026-04-17T00:00:00Z', created_at: '2026-04-17T00:00:00Z',
updated_at: '2026-04-17T00:00:00Z', updated_at: '2026-04-17T00:00:00Z',
notes: '', notes: '',
last_login_at: '2026-04-16T01:00:00Z',
last_active_at: '2026-04-16T02:00:00Z', last_active_at: '2026-04-16T02:00:00Z',
last_used_at: '2026-04-17T02:00:00Z', last_used_at: '2026-04-17T02:00:00Z',
current_concurrency: 0 current_concurrency: 0
...@@ -113,7 +112,7 @@ describe('admin UsersView', () => { ...@@ -113,7 +112,7 @@ describe('admin UsersView', () => {
getBatchUserAttributes.mockResolvedValue({ values: {} }) getBatchUserAttributes.mockResolvedValue({ values: {} })
}) })
it('shows active and used activity columns, hides last_login_at, and requests last_used_at sort', async () => { it('shows active, used, and created activity columns in order and requests last_used_at sort', async () => {
const wrapper = mount(UsersView, { const wrapper = mount(UsersView, {
global: { global: {
stubs: { stubs: {
...@@ -145,9 +144,9 @@ describe('admin UsersView', () => { ...@@ -145,9 +144,9 @@ describe('admin UsersView', () => {
await flushPromises() await flushPromises()
const columns = wrapper.get('[data-test="columns"]').text() const columns = wrapper.get('[data-test="columns"]').text()
expect(columns).toContain('last_used_at') const visibleColumns = columns.split(',')
expect(columns).toContain('last_active_at') expect(visibleColumns.slice(-4, -1)).toEqual(['last_active_at', 'last_used_at', 'created_at'])
expect(columns).not.toContain('last_login_at') expect(visibleColumns).not.toContain('last_login_at')
await wrapper.get('[data-test="sort-last-used"]').trigger('click') await wrapper.get('[data-test="sort-last-used"]').trigger('click')
await flushPromises() await flushPromises()
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment