添加短信验证服务
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-18 08:48:04 +00:00
parent 7ce61e9879
commit 680b9a3e1c
14 changed files with 321 additions and 60 deletions

View File

@@ -0,0 +1,86 @@
/* @vitest-environment jsdom */
import { render, screen } from '@testing-library/react';
import { beforeEach, expect, test, vi } from 'vitest';
import type { AuthUser } from '../../services/authService';
import { AuthGate } from './AuthGate';
const authMocks = vi.hoisted(() => ({
getStoredAccessToken: vi.fn(),
ensureAutoAuthUser: vi.fn(),
getAuthLoginOptions: 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: vi.fn(),
logoutAllAuthSessions: vi.fn(),
logoutAuthUser: vi.fn(),
revokeAuthSession: vi.fn(),
sendPhoneLoginCode: vi.fn(),
startWechatLogin: vi.fn(),
}));
vi.mock('./AccountModal', () => ({
AccountModal: () => null,
}));
vi.mock('./BindPhoneScreen', () => ({
BindPhoneScreen: () => <div></div>,
}));
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.ensureAutoAuthUser.mockResolvedValue({
user: mockUser,
credentials: {
username: 'guest_tester',
password: 'auto_password',
},
});
});
test('auth gate prefers login screen when phone login is available', async () => {
authMocks.getAuthLoginOptions.mockResolvedValue({
availableLoginMethods: ['phone'],
});
render(
<AuthGate>
<div></div>
</AuthGate>,
);
expect(await screen.findByText('账号登录')).toBeTruthy();
expect(screen.getByText('手机号')).toBeTruthy();
expect(authMocks.ensureAutoAuthUser).not.toHaveBeenCalled();
});

View File

@@ -121,6 +121,45 @@ export function AuthGate({ children }: AuthGateProps) {
return options;
};
const resolveGuestFallback = async () => {
try {
const options = await loadLoginOptions();
if (!isActive) {
return;
}
if (
allowDevGuestAutoAuth &&
options &&
options.availableLoginMethods.length === 0
) {
await ensureAutoUser();
return;
}
setUser(null);
setStatus('unauthenticated');
} catch (optionsError) {
if (!isActive) {
return;
}
if (allowDevGuestAutoAuth) {
await ensureAutoUser();
return;
}
setAvailableLoginMethods([]);
setUser(null);
setError(
optionsError instanceof Error
? optionsError.message
: '读取登录方式失败,请稍后再试。',
);
setStatus('unauthenticated');
}
};
const callbackResult = consumeAuthCallbackResult();
if (callbackResult?.error && isActive) {
setError(callbackResult.error);
@@ -128,31 +167,7 @@ export function AuthGate({ children }: AuthGateProps) {
const token = getStoredAccessToken();
if (!token) {
if (allowDevGuestAutoAuth) {
await ensureAutoUser();
return;
}
if (!isActive) {
return;
}
setUser(null);
try {
await loadLoginOptions();
} catch (optionsError) {
if (!isActive) {
return;
}
setAvailableLoginMethods([]);
setError(
optionsError instanceof Error
? optionsError.message
: '读取登录方式失败,请稍后再试。',
);
}
setStatus('unauthenticated');
await resolveGuestFallback();
return;
}
@@ -182,27 +197,7 @@ export function AuthGate({ children }: AuthGateProps) {
return;
}
if (allowDevGuestAutoAuth) {
await ensureAutoUser();
return;
}
setUser(null);
try {
await loadLoginOptions();
} catch (optionsError) {
if (!isActive) {
return;
}
setAvailableLoginMethods([]);
setError(
optionsError instanceof Error
? optionsError.message
: '读取登录方式失败,请稍后再试。',
);
}
setStatus('unauthenticated');
await resolveGuestFallback();
}
};