import { App as AntApp, ConfigProvider } from 'antd'; import { render, screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { createMemoryRouter, RouterProvider } from 'react-router-dom'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { AppContext, type AppContextValue } from '../../src/frontend/app-context'; import { I18nProvider } from '../../src/frontend/i18n/react'; import { api } from '../../src/frontend/services/api'; import { mailhubTheme } from '../../src/frontend/theme'; import AdminPage from '../../src/pages/Admin'; import type { AdminResourceInventory, AdminUser, Domain, RuntimeConfig } from '../../src/frontend/types'; describe('Admin resource migration', () => { afterEach(() => vi.restoreAllMocks()); it('submits a domain transfer after selecting the domain and target user', async () => { const user = userEvent.setup(); const transferDomain = vi.spyOn(api, 'transferAdminDomain').mockResolvedValue({ domain }); vi.spyOn(api, 'adminUsers').mockResolvedValue({ users }); vi.spyOn(api, 'adminResources').mockResolvedValue({ inventory }); renderAdminResourcesRoute(); const transferCard = (await screen.findByText('迁移域名')).closest('.ant-card'); expect(transferCard).not.toBeNull(); await selectOption(user, within(transferCard!).getByLabelText('域名'), 'taobiba.com · chendeben'); await selectOption(user, within(transferCard!).getByLabelText('目标用户'), 'chan (#7)'); await user.click(within(transferCard!).getByRole('button', { name: '执行迁移' })); const dialog = await screen.findByRole('dialog'); expect(dialog.textContent).toContain('确认迁移该域名?'); await user.click(within(dialog).getByRole('button', { name: /确\s*认/ })); await waitFor(() => expect(transferDomain).toHaveBeenCalledWith(12, { targetUserId: 7, dnsCredentialMode: 'domain_only' })); }); it('submits a DNS credential transfer after confirming the action', async () => { const user = userEvent.setup(); const transferCredential = vi.spyOn(api, 'transferAdminDnsCredential').mockResolvedValue({ credential: inventory.users[0].dnsCredentials[0] }); vi.spyOn(api, 'adminUsers').mockResolvedValue({ users }); vi.spyOn(api, 'adminResources').mockResolvedValue({ inventory }); renderAdminResourcesRoute(); const transferCard = (await screen.findByText('迁移 DNS 凭据')).closest('.ant-card'); expect(transferCard).not.toBeNull(); await selectOption(user, within(transferCard!).getByLabelText('DNS 凭据'), 'DNSPod · taobiba.com · chendeben'); await selectOption(user, within(transferCard!).getByLabelText('目标用户'), 'chan (#7)'); await user.click(within(transferCard!).getByRole('button', { name: '执行迁移' })); const dialog = await screen.findByRole('dialog'); expect(dialog.textContent).toContain('确认迁移该 DNS 凭据?'); await user.click(within(dialog).getByRole('button', { name: /确\s*认/ })); await waitFor(() => expect(transferCredential).toHaveBeenCalledWith(8, { targetUserId: 7 })); }); }); async function selectOption(user: ReturnType, input: HTMLElement, option: string) { await user.click(input); await user.click(await screen.findByText(option, { selector: '.ant-select-item-option-content' })); } function renderAdminResourcesRoute() { const router = createMemoryRouter([{ path: '/admin/:section', element: }], { initialEntries: ['/admin/resources'] }); return render( ); } const resourceCounts = { domains: 1, dnsCredentials: 1, apiTokens: 0, inboundMailboxes: 0, inboundMessages: 0, sendEvents: 0, smtpCredential: 0 }; const users: AdminUser[] = [ { id: 1, username: 'chendeben', email: 'owner@example.test', role: 'admin', status: 'active', resourceCounts }, { id: 7, username: 'chan', email: 'chan@example.test', role: 'user', status: 'active', resourceCounts } ]; const domain: Domain = { id: 12, userId: 1, dnsCredentialId: 8, smtpRelayId: null, domain: 'taobiba.com', selector: 'mailhub', verificationToken: 'verify-token', dkimPublic: 'public-key', senderHost: 'mail.taobiba.com', sendingIp: '192.0.2.10', spfExtra: '', dmarcPolicy: 'none', dmarcRua: '', catchAllAddress: '', mailboxSignupEnabled: false, status: {}, createdAt: '2026-07-19T00:00:00.000Z', updatedAt: '2026-07-19T00:00:00.000Z' }; const inventory: AdminResourceInventory = { users: [ { user: users[0], domains: [domain], dnsCredentials: [{ id: 8, userId: 1, name: 'DNSPod', provider: 'dnspod', zoneName: 'taobiba.com', defaultTtl: 600, credentialSet: true, createdAt: '2026-07-19T00:00:00.000Z', updatedAt: '2026-07-19T00:00:00.000Z' }], smtpCredential: null, apiTokens: [], inboundMailboxes: [], inboundMessageCount: 0, sendEventCount: 0 } ], warnings: [] }; const runtimeConfig: RuntimeConfig = { appBaseUrl: 'https://mail.example.test', mailHostname: 'mail.example.test', sendingIp: '192.0.2.10', defaultSpfMechanisms: '', dmarcPolicy: 'none', dmarcRua: '', registrationRequiresApproval: false, sendRequiresVerified: true, engagementTrackingEnabled: true, listUnsubscribeMailto: '', listUnsubscribeUrl: '', listUnsubscribePostEnabled: false, feedbackIdEnabled: false, reportAbuseTo: '', csaComplaintsTo: '', bounceAddress: '', bounceEnvelopeEnabled: false }; const appContext: AppContextValue = { user: users[0], config: runtimeConfig, refreshBootstrap: vi.fn(async () => undefined), logout: vi.fn(async () => undefined) };