| import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' |
| import { render, screen } from '@testing-library/react' |
| import { I18nProvider } from '../i18n' |
| import { FederatedStatusCard } from './FederatedStatusCard' |
| import * as ai from '../api/ai' |
|
|
| vi.mock('../api/ai') |
|
|
| const status: ai.FederatedStatus = { |
| client_type: 'SYNTHETIC_SERVER_SIMULATION', |
| real_device_training: 'NOT_IMPLEMENTED', |
| native_local_only_training: 'NOT_IMPLEMENTED', |
| real_user_data_used: false, |
| federation: 'SIMULATED', |
| secure_aggregation: 'DESIGN_ONLY', |
| differential_privacy: 'SIMULATED', |
| differential_privacy_detail: { |
| differential_privacy: 'SIMULATED', budget_exhausted: true, candidate_privacy_eligibility: 'ineligible', |
| cumulative_epsilon: 38.76, budget_epsilon: 8, production_privacy_guarantee: false, note: 'simulated only', |
| }, |
| automatic_model_promotion: false, affects_payment_authorization: false, |
| candidate_status: 'candidate', candidate_promotable: false, |
| disclaimer: 'Server-side simulation using synthetic clients; not real cross-device federated learning.', |
| label: 'SIMULATED', aggregators: ['coordinate_median', 'fedavg', 'trimmed_mean'], |
| } |
|
|
| const renderCard = (detailed = false) => |
| render(<I18nProvider initial="en"><FederatedStatusCard detailed={detailed} /></I18nProvider>) |
|
|
| describe('FederatedStatusCard', () => { |
| beforeEach(() => { vi.spyOn(ai, 'federatedStatus').mockResolvedValue(status) }) |
| afterEach(() => vi.restoreAllMocks()) |
|
|
| it('reports synthetic server simulation + design-only + not-implemented labels', async () => { |
| renderCard() |
| expect(await screen.findByTestId('fed-label-client_type')).toHaveTextContent('SYNTHETIC_SERVER_SIMULATION') |
| expect(screen.getByTestId('fed-label-secure_aggregation')).toHaveTextContent('DESIGN_ONLY') |
| expect(screen.getByTestId('fed-label-real_device_training')).toHaveTextContent('NOT_IMPLEMENTED') |
| expect(screen.getByTestId('fed-label-native_local_only_training')).toHaveTextContent('NOT_IMPLEMENTED') |
| }) |
|
|
| it('surfaces the exhausted simulated DP budget as a warning', async () => { |
| renderCard() |
| expect(await screen.findByTestId('dp-exhausted')).toHaveTextContent(/budget is exhausted/i) |
| expect(screen.getByTestId('dp-exhausted')).toHaveTextContent(/not a production privacy guarantee/i) |
| }) |
|
|
| it('never claims data stays on the device', async () => { |
| const { container } = renderCard(true) |
| await screen.findByTestId('federated-status') |
| expect(container.textContent).not.toMatch(/stays (only )?on your (phone|device)/i) |
| expect(container.textContent).not.toMatch(/never leaves your device/i) |
| }) |
|
|
| it('detailed mode states the candidate is not promotable and clients are synthetic', async () => { |
| renderCard(true) |
| expect(await screen.findByTestId('fed-not-promotable')).toHaveTextContent(/not promotable/i) |
| expect(screen.getByTestId('fed-synthetic')).toHaveTextContent(/no real user data is used/i) |
| }) |
| }) |
|
|