| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- 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<typeof userEvent.setup>, 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: <AdminPage /> }], {
- initialEntries: ['/admin/resources']
- });
- return render(
- <ConfigProvider theme={{ ...mailhubTheme, token: { ...mailhubTheme.token, motion: false } }}>
- <AntApp>
- <I18nProvider>
- <AppContext.Provider value={appContext}>
- <RouterProvider router={router} />
- </AppContext.Provider>
- </I18nProvider>
- </AntApp>
- </ConfigProvider>
- );
- }
- 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)
- };
|