import React from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' const USER_ID = '11111111-1111-4111-8111-111111111111' const SUBSCRIPTION_ID = '22222222-2222-4222-8222-222222222222' const ADMIN_ID = '33333333-3333-4333-8333-333333333333' const IDEMPOTENCY_KEY = '44444444-4444-4444-8444-444444444444' const NOW = '2026-08-21T00:00:00.000Z' let mockUserRole: 'user' | 'manager' | 'admin' | 'super_admin' = 'manager' const mockNavigate = jest.fn() const mockGoBack = jest.fn() const mockListUsers = jest.fn() const mockListSubscriptions = jest.fn() const mockListAudit = jest.fn() const mockGetUser = jest.fn() const mockGetPaymentHistory = jest.fn() const mockChangeRole = jest.fn() const mockCreateSubscription = jest.fn() const mockUpdateSubscription = jest.fn() const mockDeleteSubscription = jest.fn() const mockRequireToken = jest.fn() jest.mock('@react-navigation/native', () => { const ReactValue = require('react') as typeof React return { useFocusEffect: (callback: () => void | (() => void)) => { ReactValue.useEffect(callback, [callback]) }, } }) jest.mock('react-native-safe-area-context', () => ({ useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }), })) jest.mock('@d3ro/i18n', () => ({ useI18n: () => ({ t: (key: string, params?: Record) => params === undefined ? key : `${key}:${JSON.stringify(params)}`, formatDate: () => '2026-08-21', }), })) jest.mock('../src/lib/auth-context', () => ({ useAuth: () => ({ user: { id: ADMIN_ID, email: 'admin@example.com', app_metadata: { role: mockUserRole }, }, }), })) jest.mock('../src/lib/preferences-context', () => ({ useMobilePreferences: () => ({ palette: { bg: { app: '#000', card: '#181818', inset: '#202020' }, text: { primary: '#fff', muted: '#999' }, border: { default: '#444' }, accent: { main: '#7cf', dim: '#234' }, tag: { red: '#f66', green: '#6f6', orange: '#fa6', purple: '#b7f' }, }, }), })) jest.mock('../src/theme/themed-components', () => { const { Pressable, Text, View } = require('react-native') as typeof import('react-native') return { ThemeText: ({ children, ...props }: React.PropsWithChildren>) => ( {children} ), ThemeCard: ({ children, ...props }: React.PropsWithChildren>) => ( {children} ), ThemeButton: ({ label, onPress, disabled, ...props }: { label: string onPress?: () => void disabled?: boolean [key: string]: unknown }) => ( {label} ), } }) jest.mock('../src/lib/random-id', () => ({ createUuidV4: () => IDEMPOTENCY_KEY })) jest.mock('../src/features/admin/admin-session', () => ({ requireAdminAccessToken: (...args: unknown[]) => mockRequireToken(...args), })) jest.mock('../src/features/admin/admin-service', () => { class MockAdminServiceError extends Error { code: string status: number | null retryable: boolean constructor(code: string, status: number | null, retryable: boolean) { super(code) this.code = code this.status = status this.retryable = retryable } } return { ADMIN_ROLES: ['user', 'manager', 'admin', 'super_admin'], ADMIN_TIERS: ['free', 'pro', 'pro_plus'], ADMIN_SUBSCRIPTION_STATUSES: ['active', 'canceled', 'past_due', 'expired'], AdminServiceError: MockAdminServiceError, currentAdminRole: (metadata: { role?: string } | null | undefined) => ( metadata?.role === 'manager' || metadata?.role === 'admin' || metadata?.role === 'super_admin' ? metadata.role : null ), listAdminUsers: (...args: unknown[]) => mockListUsers(...args), listAdminSubscriptions: (...args: unknown[]) => mockListSubscriptions(...args), listAdminAudit: (...args: unknown[]) => mockListAudit(...args), getAdminUser: (...args: unknown[]) => mockGetUser(...args), getAdminPaymentHistory: (...args: unknown[]) => mockGetPaymentHistory(...args), changeAdminUserRole: (...args: unknown[]) => mockChangeRole(...args), createAdminSubscription: (...args: unknown[]) => mockCreateSubscription(...args), updateAdminSubscription: (...args: unknown[]) => mockUpdateSubscription(...args), deleteAdminSubscription: (...args: unknown[]) => mockDeleteSubscription(...args), } }) import AdminScreen from '../src/screens/AdminScreen' import AdminUserDetailScreen from '../src/screens/AdminUserDetailScreen' const profile = { id: USER_ID, name: 'Target user', avatarUrl: null, locale: 'ko', tier: 'pro' as const, role: 'user' as const, createdAt: NOW, updatedAt: NOW, } const subscription = { id: SUBSCRIPTION_ID, userId: USER_ID, tier: 'pro' as const, status: 'active' as const, provider: 'admin', paymentProvider: 'admin', currentPeriodStart: NOW, currentPeriodEnd: '2026-09-21T00:00:00.000Z', overageCredits: 2, adminNote: null, cancelAt: null, createdAt: NOW, updatedAt: NOW, } async function flush(): Promise { await act(async () => { await Promise.resolve() await Promise.resolve() await Promise.resolve() }) } describe('mobile administrator screens', () => { beforeEach(() => { mockUserRole = 'manager' jest.clearAllMocks() mockRequireToken.mockResolvedValue('authenticated-admin-access-token') mockListUsers.mockResolvedValue({ entries: [profile], total: 1, page: 1, limit: 20 }) mockListSubscriptions.mockResolvedValue({ entries: [subscription], total: 1, page: 1, limit: 20 }) mockListAudit.mockResolvedValue({ entries: [], total: 0, page: 1, limit: 20 }) mockGetUser.mockResolvedValue({ profile, subscription, account: { id: USER_ID, email: 'target@example.com', createdAt: NOW, lastSignInAt: NOW }, }) mockGetPaymentHistory.mockResolvedValue({ subscription, auditLogs: [], providerEvents: [], providerOperations: [], liveProviderHistoryAvailable: false, }) mockUpdateSubscription.mockResolvedValue(subscription) }) it('fails closed before any request for a non-administrator session', async () => { mockUserRole = 'user' let renderer: ReactTestRenderer await act(async () => { renderer = create() }) await flush() expect(renderer!.root.findByProps({ testID: 'admin-forbidden' })).toBeTruthy() expect(mockRequireToken).not.toHaveBeenCalled() expect(mockListUsers).not.toHaveBeenCalled() act(() => renderer!.unmount()) }) it('loads real server users and opens only the selected user detail', async () => { let renderer: ReactTestRenderer await act(async () => { renderer = create() }) await flush() expect(mockRequireToken).toHaveBeenCalledWith(ADMIN_ID) expect(mockListUsers).toHaveBeenCalledWith(expect.objectContaining({ page: 1, limit: 20 })) const resultsScroll = renderer!.root.find( (node) => node.props.keyboardShouldPersistTaps === 'handled', ) expect(resultsScroll.props.keyboardDismissMode).toBe('on-drag') const row = renderer!.root.findByProps({ testID: `admin-user-${USER_ID}` }) act(() => row.props.onPress()) expect(mockNavigate).toHaveBeenCalledWith('AdminUserDetail', { userId: USER_ID }) act(() => renderer!.unmount()) }) it('purges previously rendered administrator data when the server revokes access', async () => { const { AdminServiceError } = jest.requireMock('../src/features/admin/admin-service') let renderer: ReactTestRenderer await act(async () => { renderer = create() }) await flush() expect(renderer!.root.findByProps({ testID: `admin-user-${USER_ID}` })).toBeTruthy() mockListUsers.mockRejectedValueOnce(new AdminServiceError('forbidden', 403, false)) const scroll = renderer!.root.find((node) => node.props.refreshControl !== undefined) await act(async () => { scroll.props.refreshControl.props.onRefresh() await Promise.resolve() await Promise.resolve() }) expect(renderer!.root.findByProps({ testID: 'admin-error' })).toBeTruthy() expect(() => renderer!.root.findByProps({ testID: `admin-user-${USER_ID}` })).toThrow() act(() => renderer!.unmount()) }) it('lets a manager update an existing subscription but not change roles or delete it', async () => { let renderer: ReactTestRenderer await act(async () => { renderer = create() }) await flush() expect(() => renderer!.root.findByProps({ testID: 'admin-change-role' })).toThrow() expect(() => renderer!.root.findByProps({ testID: 'admin-delete-subscription' })).toThrow() const edit = renderer!.root.findByProps({ testID: 'admin-edit-subscription' }) act(() => edit.props.onPress()) const memo = renderer!.root.findByProps({ testID: 'admin-memo-input' }) act(() => memo.props.onChangeText('verified customer request')) const submit = renderer!.root.findByProps({ testID: 'admin-mutation-submit' }) await act(async () => { submit.props.onPress() await Promise.resolve() await Promise.resolve() }) expect(mockUpdateSubscription).toHaveBeenCalledWith(expect.objectContaining({ accessToken: 'authenticated-admin-access-token', idempotencyKey: IDEMPOTENCY_KEY, userId: USER_ID, mutation: expect.objectContaining({ memo: 'verified customer request' }), })) act(() => renderer!.unmount()) }) it('locks input after an uncertain failure and retries the exact idempotency key', async () => { mockUserRole = 'admin' mockUpdateSubscription .mockRejectedValueOnce(new (jest.requireMock('../src/features/admin/admin-service').AdminServiceError)('timeout', null, true)) .mockResolvedValueOnce(subscription) let renderer: ReactTestRenderer await act(async () => { renderer = create() }) await flush() act(() => renderer!.root.findByProps({ testID: 'admin-edit-subscription' }).props.onPress()) act(() => renderer!.root.findByProps({ testID: 'admin-memo-input' }).props.onChangeText('network response lost')) await act(async () => { renderer!.root.findByProps({ testID: 'admin-mutation-submit' }).props.onPress() await Promise.resolve() await Promise.resolve() }) expect(renderer!.root.findByProps({ testID: 'admin-memo-input' }).props.editable).toBe(false) expect(renderer!.root.findByProps({ testID: 'admin-mutation-error' })).toBeTruthy() await act(async () => { renderer!.root.findByProps({ testID: 'admin-mutation-submit' }).props.onPress() await Promise.resolve() await Promise.resolve() }) expect(mockUpdateSubscription).toHaveBeenCalledTimes(2) expect(mockUpdateSubscription.mock.calls[0][0]).toEqual(mockUpdateSubscription.mock.calls[1][0]) act(() => renderer!.unmount()) }) })