/* @vitest-environment jsdom */ import { render, screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { beforeEach, expect, test, vi } from 'vitest'; import type { AuthUser } from '../../services/authService'; import { AuthGate } from './AuthGate'; import { useAuthUi } from './AuthUiContext'; const authMocks = vi.hoisted(() => ({ getStoredAccessToken: vi.fn(), ensureAutoAuthUser: vi.fn(), getAuthLoginOptions: vi.fn(), loginWithPhoneCode: vi.fn(), sendPhoneLoginCode: vi.fn(), startWechatLogin: vi.fn(), consumeAuthCallbackResult: vi.fn(), })); vi.mock('../../services/apiClient', () => ({ AUTH_STATE_EVENT: 'genarrative-auth-state-changed', getStoredAccessToken: authMocks.getStoredAccessToken, })); vi.mock('../../services/authService', () => ({ bindWechatPhone: vi.fn(), changePhoneNumber: vi.fn(), consumeAuthCallbackResult: authMocks.consumeAuthCallbackResult, ensureAutoAuthUser: authMocks.ensureAutoAuthUser, getAuthAuditLogs: vi.fn(), getAuthLoginOptions: authMocks.getAuthLoginOptions, getAuthRiskBlocks: vi.fn(), getAuthSessions: vi.fn(), getCaptchaChallengeFromError: vi.fn(() => null), getCurrentAuthUser: vi.fn(), liftAuthRiskBlock: vi.fn(), loginWithPhoneCode: authMocks.loginWithPhoneCode, logoutAllAuthSessions: vi.fn(), logoutAuthUser: vi.fn(), revokeAuthSession: vi.fn(), sendPhoneLoginCode: authMocks.sendPhoneLoginCode, startWechatLogin: authMocks.startWechatLogin, })); vi.mock('../../hooks/useGameSettings', () => ({ useGameSettings: () => ({ musicVolume: 0.42, setMusicVolume: () => {}, platformTheme: 'light', setPlatformTheme: () => {}, hasHydratedSettings: true, isHydratingSettings: false, isPersistingSettings: false, settingsError: null, }), })); vi.mock('./AccountModal', () => ({ AccountModal: () => null, })); vi.mock('./BindPhoneScreen', () => ({ BindPhoneScreen: () =>
绑定手机号
, })); const mockUser: AuthUser = { id: 'user-1', username: 'tester', displayName: '测试玩家', phoneNumberMasked: '138****8000', loginMethod: 'phone', bindingStatus: 'active', wechatBound: false, }; beforeEach(() => { vi.clearAllMocks(); authMocks.getStoredAccessToken.mockReturnValue(null); authMocks.consumeAuthCallbackResult.mockReturnValue(null); authMocks.loginWithPhoneCode.mockResolvedValue(mockUser); authMocks.sendPhoneLoginCode.mockResolvedValue({ cooldownSeconds: 60, expiresInSeconds: 300, }); authMocks.startWechatLogin.mockResolvedValue(undefined); authMocks.ensureAutoAuthUser.mockResolvedValue({ user: mockUser, credentials: { username: 'guest_tester', password: 'auto_password', }, }); }); function ProtectedActionButton({ onAuthenticated }: { onAuthenticated: () => void }) { const authUi = useAuthUi(); return ( ); } test('auth gate keeps platform content visible when phone login is available', async () => { authMocks.getAuthLoginOptions.mockResolvedValue({ availableLoginMethods: ['phone'], }); render(
应用内容
, ); expect(await screen.findByText('应用内容')).toBeTruthy(); expect(screen.queryByRole('button', { name: '登录' })).toBeNull(); expect(screen.queryByText('先登录账号,再同步你的冒险进度。')).toBeNull(); expect(authMocks.ensureAutoAuthUser).not.toHaveBeenCalled(); }); test('auth gate does not auto-create a guest account when dev guest switch is not explicitly enabled', async () => { authMocks.getAuthLoginOptions.mockResolvedValue({ availableLoginMethods: [], }); render(
应用内容
, ); expect(await screen.findByText('应用内容')).toBeTruthy(); expect(authMocks.ensureAutoAuthUser).not.toHaveBeenCalled(); }); test('auth gate opens a login modal for protected actions and resumes after login', async () => { const user = userEvent.setup(); const onAuthenticated = vi.fn(); authMocks.getAuthLoginOptions.mockResolvedValue({ availableLoginMethods: ['phone'], }); render( , ); await user.click(await screen.findByRole('button', { name: '进入作品' })); const dialog = screen.getByRole('dialog', { name: '登录账号' }); expect(dialog).toBeTruthy(); expect(screen.queryByText('先登录账号,再同步你的冒险进度。')).toBeNull(); await user.type(within(dialog).getByLabelText('手机号'), '13800000000'); await user.type(within(dialog).getByLabelText('验证码'), '123456'); await user.click(within(dialog).getByRole('button', { name: '登录' })); await waitFor(() => { expect(authMocks.loginWithPhoneCode).toHaveBeenCalledWith( '13800000000', '123456', ); expect(onAuthenticated).toHaveBeenCalledTimes(1); }); expect(screen.queryByRole('dialog', { name: '登录账号' })).toBeNull(); }); test('auth gate shows sms send feedback in the login modal', async () => { const user = userEvent.setup(); authMocks.getAuthLoginOptions.mockResolvedValue({ availableLoginMethods: ['phone'], }); render( , ); await user.click(await screen.findByRole('button', { name: '进入作品' })); const dialog = screen.getByRole('dialog', { name: '登录账号' }); await user.type(within(dialog).getByLabelText('手机号'), '13800000000'); await user.click(within(dialog).getByRole('button', { name: '获取验证码' })); await waitFor(() => { expect(authMocks.sendPhoneLoginCode).toHaveBeenCalledWith( '13800000000', 'login', { challengeId: undefined, answer: '', }, ); }); expect( within(dialog).getByText('验证码已发送,有效期约 5 分钟。'), ).toBeTruthy(); expect(within(dialog).getByRole('button', { name: '60s' })).toBeTruthy(); });