86
src/components/auth/AuthGate.test.tsx
Normal file
86
src/components/auth/AuthGate.test.tsx
Normal 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();
|
||||
});
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user