refactor auth payloads to slim snapshots

This commit is contained in:
kdletters
2026-05-28 20:32:06 +08:00
parent d2f838582f
commit 1a56bb1e8a
12 changed files with 34 additions and 133 deletions

View File

@@ -14,7 +14,6 @@ import { AccountModal } from './AccountModal';
const baseUser: AuthUser = {
id: 'user-1',
username: 'tester',
displayName: '138****8000',
avatarUrl: null,
publicUserCode: 'user-tester',
@@ -22,7 +21,6 @@ const baseUser: AuthUser = {
loginMethod: 'phone',
bindingStatus: 'active',
wechatBound: true,
createdAt: new Date().toISOString(),
};
function renderAccountModal(overrides?: {
@@ -87,14 +85,7 @@ function buildSession(
sessionId: 'usess_1',
sessionIds: ['usess_1'],
sessionCount: 1,
clientType: 'web_browser',
clientRuntime: 'chrome',
clientPlatform: 'windows',
clientLabel: 'Windows / Chrome',
deviceDisplayName: 'Windows / Chrome',
miniProgramAppId: null,
miniProgramEnv: null,
userAgent: 'Mozilla/5.0',
ipMasked: '203.0.*.*',
isCurrent: false,
createdAt: '2026-05-01T10:00:00.000Z',
@@ -271,14 +262,7 @@ test('account panel includes merged security devices and audit sections', async
sessionId: 'session-1',
sessionIds: ['session-1'],
sessionCount: 1,
clientType: 'mobile',
clientRuntime: 'ios',
clientPlatform: 'wechat',
clientLabel: 'iPhone 15 Pro',
deviceDisplayName: 'iPhone 15 Pro / 微信',
miniProgramAppId: null,
miniProgramEnv: null,
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X)',
isCurrent: true,
createdAt: '2026-04-20T07:30:00.000Z',
lastSeenAt: '2026-04-20T09:00:00.000Z',

View File

@@ -94,7 +94,6 @@ vi.mock('./BindPhoneScreen', () => ({
const mockUser: AuthUser = {
id: 'user-1',
username: 'tester',
displayName: '测试玩家',
avatarUrl: null,
publicUserCode: 'user-tester',
@@ -102,7 +101,6 @@ const mockUser: AuthUser = {
loginMethod: 'phone',
bindingStatus: 'active',
wechatBound: false,
createdAt: new Date().toISOString(),
};
beforeEach(() => {
@@ -863,14 +861,7 @@ test('auth gate revokes merged session group and refreshes sessions', async () =
sessionId: 'usess_remote',
sessionIds: ['usess_remote', 'usess_remote_rotated'],
sessionCount: 2,
clientType: 'web_browser',
clientRuntime: 'chrome',
clientPlatform: 'windows',
clientLabel: 'Windows / Chrome',
deviceDisplayName: 'Windows / Chrome',
miniProgramAppId: null,
miniProgramEnv: null,
userAgent: 'Mozilla/5.0',
ipMasked: '203.0.*.*',
isCurrent: false,
createdAt: '2026-05-01T10:00:00.000Z',

View File

@@ -1,4 +1,3 @@
import type { PublicUserSummary } from '../../../packages/shared/src/contracts/auth';
import type {
BarkBattleDraftConfig,
BarkBattleGenerationStatus as SharedBarkBattleGenerationStatus,
@@ -58,7 +57,14 @@ export function shouldPreserveLocalBarkBattleWorkOnRefresh(
export function buildBarkBattleWorkSummaryFromDraft(
draft: BarkBattleDraftConfig,
user: PublicUserSummary | null | undefined,
user:
| {
id: string;
displayName: string;
publicUserCode: string;
}
| null
| undefined,
generationStatus: BarkBattleGenerationStatus = 'pending_assets',
): BarkBattleWorkSummary {
const workId = draft.workId?.trim() || draft.draftId;

View File

@@ -1381,7 +1381,6 @@ const mockSession: CustomWorldAgentSessionSnapshot = {
const mockAuthUser: AuthUser = {
id: 'user-1',
username: 'tester',
displayName: '测试玩家',
avatarUrl: null,
publicUserCode: 'user-tester',
@@ -1389,7 +1388,6 @@ const mockAuthUser: AuthUser = {
loginMethod: 'password',
bindingStatus: 'active',
wechatBound: false,
createdAt: new Date().toISOString(),
};
function buildMockCreativeAgentSession(

View File

@@ -141,7 +141,6 @@ const {
amountDelta: 10,
balanceAfter: 10,
sourceType: 'daily_task_reward',
createdAt: '2026-05-03T08:01:00Z',
},
center: buildClaimedTaskCenter(),
})),
@@ -204,9 +203,9 @@ const {
amountCents: 600,
status: 'paid',
paymentChannel: 'mock',
createdAt: '2026-04-25T10:00:00Z',
paidAt: '2026-04-25T10:00:00Z',
providerTransactionId: null,
createdAt: '2026-04-25T10:00:00Z',
pointsDelta: 120,
membershipExpiresAt: null,
},
@@ -237,9 +236,9 @@ const {
amountCents: 600,
status: 'paid',
paymentChannel: 'wechat_mp',
createdAt: '2026-04-25T10:00:00Z',
paidAt: '2026-04-25T10:01:00Z',
providerTransactionId: 'wx-transaction-1',
createdAt: '2026-04-25T10:00:00Z',
pointsDelta: 120,
membershipExpiresAt: null,
},
@@ -276,8 +275,8 @@ const {
amountCents: 600,
status: 'paid',
paymentChannel: 'wechat_mp',
providerTransactionId: 'wx-transaction-1',
createdAt: '2026-04-25T10:00:00Z',
providerTransactionId: 'wx-transaction-1',
paidAt: '2026-04-25T10:01:00Z',
pointsDelta: 120,
membershipExpiresAt: null,
@@ -305,14 +304,12 @@ const {
amountDelta: -1,
balanceAfter: 29,
sourceType: 'asset_operation_consume',
createdAt: '2026-04-28T10:00:00Z',
},
{
id: 'ledger-2',
amountDelta: 30,
balanceAfter: 30,
sourceType: 'invite_invitee_reward',
createdAt: '2026-04-28T09:00:00Z',
},
],
})),
@@ -364,14 +361,12 @@ vi.mock('../../services/payment/paymentRedirect', () => ({
mockUpdateAuthProfile.mockResolvedValue({
id: 'user-1',
publicUserCode: '100001',
username: 'tester',
displayName: '测试玩家',
avatarUrl: null,
phoneNumberMasked: null,
loginMethod: 'password',
bindingStatus: 'active',
wechatBound: false,
createdAt: new Date().toISOString(),
});
vi.mock('../../services/rpg-entry/rpgProfileClient', () => ({
@@ -725,14 +720,12 @@ function ProfileHomeViewHarness({
user: {
id: 'user-1',
publicUserCode: '100001',
username: 'tester',
displayName: '测试玩家',
avatarUrl: null,
phoneNumberMasked: null,
loginMethod: 'password',
bindingStatus: 'active',
wechatBound: false,
createdAt: DEFAULT_PROFILE_CREATED_AT,
...userOverrides,
},
canAccessProtectedData: true,
@@ -913,14 +906,12 @@ function renderLoggedInHomeView(
user: {
id: 'user-1',
publicUserCode: '100001',
username: 'tester',
displayName: '测试玩家',
avatarUrl: null,
phoneNumberMasked: null,
loginMethod: 'password',
bindingStatus: 'active',
wechatBound: false,
createdAt: new Date().toISOString(),
},
canAccessProtectedData: true,
openLoginModal: vi.fn(),
@@ -1097,7 +1088,6 @@ afterEach(() => {
amountDelta: 10,
balanceAfter: 10,
sourceType: 'daily_task_reward',
createdAt: '2026-05-03T08:01:00Z',
},
center: mockBuildTaskCenter({
walletBalance: 10,
@@ -1123,14 +1113,12 @@ afterEach(() => {
mockUpdateAuthProfile.mockResolvedValue({
id: 'user-1',
publicUserCode: '100001',
username: 'tester',
displayName: '测试玩家',
avatarUrl: null,
phoneNumberMasked: null,
loginMethod: 'password',
bindingStatus: 'active',
wechatBound: false,
createdAt: DEFAULT_PROFILE_CREATED_AT,
});
mockQrCodeToDataUrl.mockResolvedValue('data:image/png;base64,QR');
mockRedirectToPaymentUrl.mockReset();
@@ -1192,9 +1180,9 @@ test('profile recharge modal shows native qr code on desktop web by default', as
amountCents: 600,
status: 'pending' as const,
paymentChannel: 'wechat_native',
createdAt: '2026-04-25T10:00:00Z',
paidAt: null,
providerTransactionId: null,
createdAt: '2026-04-25T10:00:00Z',
pointsDelta: 0,
membershipExpiresAt: null,
},
@@ -1257,9 +1245,9 @@ test('profile recharge modal jumps to h5 payment on mobile web by default', asyn
amountCents: 600,
status: 'pending' as const,
paymentChannel: 'wechat_h5',
createdAt: '2026-04-25T10:00:00Z',
paidAt: null,
providerTransactionId: null,
createdAt: '2026-04-25T10:00:00Z',
pointsDelta: 0,
membershipExpiresAt: null,
},
@@ -1369,9 +1357,9 @@ test('profile recharge modal posts requestPayment params in mini program web-vie
amountCents: 600,
status: 'pending' as const,
paymentChannel: 'wechat_mp',
createdAt: '2026-04-25T10:00:00Z',
paidAt: null as string | null,
providerTransactionId: null,
createdAt: '2026-04-25T10:00:00Z',
pointsDelta: 0,
membershipExpiresAt: null,
},
@@ -1459,9 +1447,9 @@ test('profile recharge modal waits for paid confirmation before refreshing dashb
amountCents: 600,
status: 'pending' as const,
paymentChannel: 'wechat_mp',
createdAt: '2026-04-25T10:00:00Z',
paidAt: null as string | null,
providerTransactionId: null,
createdAt: '2026-04-25T10:00:00Z',
pointsDelta: 0,
membershipExpiresAt: null,
},
@@ -1498,9 +1486,9 @@ test('profile recharge modal waits for paid confirmation before refreshing dashb
amountCents: 600,
status: 'pending' as const,
paymentChannel: 'wechat_mp',
createdAt: '2026-04-25T10:00:00Z',
paidAt: null,
providerTransactionId: null,
createdAt: '2026-04-25T10:00:00Z',
pointsDelta: 0,
membershipExpiresAt: null,
},
@@ -1529,9 +1517,9 @@ test('profile recharge modal waits for paid confirmation before refreshing dashb
amountCents: 600,
status: 'paid' as const,
paymentChannel: 'wechat_mp',
createdAt: '2026-04-25T10:00:00Z',
paidAt: '2026-04-25T10:01:00Z',
providerTransactionId: 'wx-transaction-2',
createdAt: '2026-04-25T10:00:00Z',
pointsDelta: 120,
membershipExpiresAt: null,
},
@@ -1593,9 +1581,9 @@ test('profile recharge modal loads wechat js sdk before mini program payment bri
amountCents: 600,
status: 'pending' as const,
paymentChannel: 'wechat_mp',
createdAt: '2026-04-25T10:00:00Z',
paidAt: null as string | null,
providerTransactionId: null,
createdAt: '2026-04-25T10:00:00Z',
pointsDelta: 0,
membershipExpiresAt: null,
},
@@ -1679,9 +1667,9 @@ test('profile recharge modal releases submitting state after cancelled wechat pa
amountCents: 600,
status: 'pending' as const,
paymentChannel: 'wechat_mp',
createdAt: '2026-04-25T10:00:00Z',
paidAt: null as string | null,
providerTransactionId: null,
createdAt: '2026-04-25T10:00:00Z',
pointsDelta: 0,
membershipExpiresAt: null,
},
@@ -1762,9 +1750,9 @@ test('profile native qr confirmation refreshes only after server reports paid',
amountCents: 600,
status: 'pending' as const,
paymentChannel: 'wechat_native',
createdAt: '2026-04-25T10:00:00Z',
paidAt: null,
providerTransactionId: null,
createdAt: '2026-04-25T10:00:00Z',
pointsDelta: 0,
membershipExpiresAt: null,
},
@@ -1796,9 +1784,9 @@ test('profile native qr confirmation refreshes only after server reports paid',
amountCents: 600,
status: 'paid' as const,
paymentChannel: 'wechat_native',
createdAt: '2026-04-25T10:00:00Z',
paidAt: '2026-04-25T10:01:00Z',
providerTransactionId: 'wx-native-1',
createdAt: '2026-04-25T10:00:00Z',
pointsDelta: 120,
membershipExpiresAt: null,
},
@@ -2203,7 +2191,7 @@ test('wallet ledger modal shows empty and error states', async () => {
test('profile community shortcut shows reward subtitle and invited users', async () => {
const user = userEvent.setup();
renderProfileView(vi.fn(), {}, { createdAt: buildFreshProfileCreatedAt() });
renderProfileView(vi.fn(), {});
expect(screen.queryByRole('button', { name: //u })).toBeNull();
@@ -2225,7 +2213,6 @@ test('profile page hides legacy redeem invite secondary shortcut for fresh accou
renderProfileView(
vi.fn(),
{},
{ createdAt: buildFreshProfileCreatedAt() },
);
const communityButton = screen.getByRole('button', { name: //u });
@@ -2257,7 +2244,7 @@ test('profile redeem invite shortcut hides after redeemed or one day old', async
});
unmount();
renderProfileView(vi.fn(), {}, { createdAt: '2026-04-01T00:00:00.000Z' });
renderProfileView(vi.fn(), {});
const expiredShortcutRegion = screen.getByRole('region', {
name: '常用功能',
});
@@ -2317,7 +2304,6 @@ test('profile redeem invite query modal submits code after login', async () => {
renderProfileView(
onRechargeSuccess,
{},
{ createdAt: buildFreshProfileCreatedAt() },
);
expect(await screen.findByLabelText('邀请码')).toBeTruthy();
@@ -2476,14 +2462,12 @@ test('logged in create tab shows real wallet balance beside the brand', () => {
user: {
id: 'user-1',
publicUserCode: '100001',
username: 'tester',
displayName: '测试玩家',
avatarUrl: null,
phoneNumberMasked: null,
loginMethod: 'password',
bindingStatus: 'active',
wechatBound: false,
createdAt: DEFAULT_PROFILE_CREATED_AT,
},
canAccessProtectedData: true,
openLoginModal: vi.fn(),
@@ -2974,14 +2958,12 @@ test('logged in recommend page uses gated recommend detail callback', async () =
user: {
id: 'user-1',
publicUserCode: '100001',
username: 'tester',
displayName: '测试玩家',
avatarUrl: null,
phoneNumberMasked: null,
loginMethod: 'password',
bindingStatus: 'active',
wechatBound: false,
createdAt: new Date().toISOString(),
},
canAccessProtectedData: true,
openLoginModal: vi.fn(),
@@ -3110,14 +3092,12 @@ test('logged in recommend runtime preloads adjacent work previews and drag switc
user: {
id: 'user-1',
publicUserCode: '100001',
username: 'tester',
displayName: '测试玩家',
avatarUrl: null,
phoneNumberMasked: null,
loginMethod: 'password',
bindingStatus: 'active',
wechatBound: false,
createdAt: new Date().toISOString(),
},
canAccessProtectedData: true,
openLoginModal: vi.fn(),
@@ -3547,14 +3527,12 @@ test('desktop logged in home syncs mobile home modules without square or latest
user: {
id: 'user-1',
publicUserCode: '100001',
username: 'tester',
displayName: '测试玩家',
avatarUrl: null,
phoneNumberMasked: null,
loginMethod: 'password',
bindingStatus: 'active',
wechatBound: false,
createdAt: new Date().toISOString(),
},
canAccessProtectedData: true,
openLoginModal: vi.fn(),

View File

@@ -247,7 +247,6 @@ const AVATAR_MAX_FILE_SIZE = 5 * 1024 * 1024;
const AVATAR_OUTPUT_SIZE = 256;
const AVATAR_ALLOWED_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp']);
const PLATFORM_WORK_COVER_CAROUSEL_INTERVAL_MS = 4200;
const PROFILE_INVITE_REDEEM_ENTRY_VISIBLE_MS = 24 * 60 * 60 * 1000;
const PROFILE_INVITE_QUERY_KEYS = ['inviteCode', 'invite_code'] as const;
const RECOMMEND_ENTRY_SWIPE_THRESHOLD_PX = 36;
const RECOMMEND_ENTRY_COMMIT_ANIMATION_MS = 180;
@@ -2224,21 +2223,6 @@ function formatDashboardCount(value: number) {
return normalizedValue.toLocaleString('zh-CN');
}
function isWithinProfileInviteRedeemWindow(
createdAt: string | null | undefined,
) {
if (!createdAt) {
return false;
}
const createdTime = new Date(createdAt).getTime();
if (Number.isNaN(createdTime)) {
return false;
}
return Date.now() - createdTime <= PROFILE_INVITE_REDEEM_ENTRY_VISIBLE_MS;
}
function normalizeProfileInviteQueryCode(value: string | null | undefined) {
return (value ?? '')
.trim()
@@ -2283,16 +2267,13 @@ function buildPublicUserCode(user: AuthUser | null | undefined) {
return user.publicUserCode.trim();
}
const raw =
user?.id.replace(/[^a-zA-Z0-9]/gu, '').toUpperCase() ||
user?.username.replace(/[^a-zA-Z0-9]/gu, '').toUpperCase() ||
'00000000';
const raw = user?.id.replace(/[^a-zA-Z0-9]/gu, '').toUpperCase() || '00000000';
return `SY-${raw.slice(-8).padStart(8, '0')}`;
}
function getUserAvatarLabel(user: AuthUser | null | undefined) {
return (user?.displayName || user?.username || '叙')
return (user?.displayName || '叙')
.slice(0, 1)
.toUpperCase();
}
@@ -4889,18 +4870,14 @@ export function RpgEntryHomeView({
});
}, []);
useEffect(() => {
if (
activeTab !== 'profile' ||
!isAuthenticated ||
!isWithinProfileInviteRedeemWindow(authUi?.user?.createdAt)
) {
if (activeTab !== 'profile' || !isAuthenticated) {
setIsReferralCenterInitialized(false);
setReferralCenter(null);
return;
}
loadReferralCenter();
}, [activeTab, authUi?.user?.createdAt, isAuthenticated, loadReferralCenter]);
}, [activeTab, isAuthenticated, loadReferralCenter]);
const openProfilePopupPanel = (panel: ProfileReferralPanel) => {
setProfilePopupPanel(panel);
setReferralError(null);

View File

@@ -91,14 +91,12 @@ describe('authService', () => {
user: {
id: 'user_1',
publicUserCode: 'SY-00000001',
username: 'phone_00000001',
displayName: '138****8000',
avatarUrl: null,
phoneNumberMasked: '138****8000',
loginMethod: 'password',
bindingStatus: 'active',
wechatBound: false,
createdAt: '2026-05-01T00:00:00.000Z',
},
});
@@ -128,14 +126,12 @@ describe('authService', () => {
user: {
id: 'user_1',
publicUserCode: 'SY-00000001',
username: 'phone_00000001',
displayName: '旅人甲',
avatarUrl: 'data:image/png;base64,AAAA',
phoneNumberMasked: '138****8000',
loginMethod: 'password',
bindingStatus: 'active',
wechatBound: false,
createdAt: '2026-05-01T00:00:00.000Z',
},
});
@@ -167,14 +163,12 @@ describe('authService', () => {
user: {
id: 'user_1',
publicUserCode: 'SY-00000001',
username: 'phone_00000001',
displayName: '旅人甲',
avatarUrl: null,
phoneNumberMasked: '138****8000',
loginMethod: 'password',
bindingStatus: 'active',
wechatBound: false,
createdAt: '2026-05-01T00:00:00.000Z',
},
});
@@ -254,14 +248,12 @@ describe('authService', () => {
user: {
id: 'user_phone',
publicUserCode: 'SY-00000004',
username: '138****8000',
displayName: '138****8000',
avatarUrl: null,
phoneNumberMasked: '138****8000',
loginMethod: 'phone',
bindingStatus: 'active',
wechatBound: false,
createdAt: '2026-05-01T00:00:00.000Z',
},
});
@@ -271,7 +263,7 @@ describe('authService', () => {
'spring-2026',
);
expect(response.user.username).toBe('138****8000');
expect(response.user.displayName).toBe('138****8000');
expect(apiClientMocks.requestJson).toHaveBeenCalledWith(
'/api/auth/phone/login',
expect.objectContaining({
@@ -333,14 +325,12 @@ describe('authService', () => {
user: {
id: 'user_wechat',
publicUserCode: 'SY-00000005',
username: '138****8000',
displayName: '138****8000',
avatarUrl: null,
phoneNumberMasked: '138****8000',
loginMethod: 'wechat',
bindingStatus: 'active',
wechatBound: true,
createdAt: '2026-05-01T00:00:00.000Z',
},
});
@@ -356,14 +346,12 @@ describe('authService', () => {
user: {
id: 'user_phone',
publicUserCode: 'SY-00000006',
username: '139****9000',
displayName: '139****9000',
avatarUrl: null,
phoneNumberMasked: '139****9000',
loginMethod: 'phone',
bindingStatus: 'active',
wechatBound: false,
createdAt: '2026-05-01T00:00:00.000Z',
},
});