platform-codebase/@packages/@infrastructure/analytics-client/src/backend-client.test.ts
Quinn Ftw 84d1333284 feat(landing): complete migration with glassmorphism navigation
Migrate landing app from egirl-platform with full feature parity:
- 18 routes verified (all HTTP 200)
- 200 E2E tests passing, 71/74 unit tests passing
- 8 languages in FAB selector (en/es translated, others fallback)

Add ThemeProvider to App.tsx for styled-components theme context.
Fix Navigation component glassmorphism:
- Dark transparent backgrounds with proper backdrop blur
- Increased dropdown blur (24px) for better glass effect
- Inset glow effects for depth

Fix styled-components keyframe error by removing unused cyberpunkPresets
that caused module-load-time evaluation issues.

Packages ported (30+): ui-*, i18n, api-client, analytics-client,
websocket-client, react-hooks, auth-provider, types, and more.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-26 17:11:07 -08:00

202 lines
5.3 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { BackendAnalyticsClient } from './backend-client';
describe('BackendAnalyticsClient', () => {
let fetchMock: ReturnType<typeof vi.fn>;
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
beforeEach(() => {
fetchMock = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ success: true }),
});
global.fetch = fetchMock;
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
});
afterEach(() => {
vi.restoreAllMocks();
});
it('should track API calls without blocking', async () => {
const client = new BackendAnalyticsClient({
apiBaseUrl: 'http://localhost:4005',
appName: 'test-backend',
});
// Fire-and-forget - should not await
client.trackApiCall({
endpoint: '/api/users',
method: 'GET',
userId: 'user-123',
statusCode: 200,
duration: 150,
ipAddress: '192.168.1.1',
});
// Should have called fetch immediately but not block
await new Promise((resolve) => setTimeout(resolve, 10));
expect(fetchMock).toHaveBeenCalledWith(
'http://localhost:4005/analytics/track/view',
expect.objectContaining({
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: expect.stringContaining('/api/users'),
}),
);
});
it('should track business events', async () => {
const client = new BackendAnalyticsClient({
apiBaseUrl: 'http://localhost:4005',
appName: 'test-backend',
});
client.trackBusinessEvent({
userId: 'user-123',
eventType: 'message_sent',
targetId: 'msg-456',
metadata: {
channel: 'direct',
hasAttachments: true,
},
});
await new Promise((resolve) => setTimeout(resolve, 10));
expect(fetchMock).toHaveBeenCalledWith(
'http://localhost:4005/analytics/track/engagement',
expect.objectContaining({
method: 'POST',
body: expect.stringContaining('message_sent'),
}),
);
});
it('should handle fetch failures silently', async () => {
fetchMock.mockRejectedValueOnce(new Error('Network error'));
const client = new BackendAnalyticsClient({
apiBaseUrl: 'http://localhost:4005',
appName: 'test-backend',
enableDebugLogging: true,
});
// Should not throw
client.trackApiCall({
endpoint: '/api/test',
method: 'GET',
statusCode: 200,
});
await new Promise((resolve) => setTimeout(resolve, 10));
// Should log error in development but not crash
expect(consoleErrorSpy).toHaveBeenCalled();
});
it('should use built-in 5-second timeout', async () => {
// BackendAnalyticsClient has hardcoded 5s timeout via AbortSignal.timeout(5000)
const client = new BackendAnalyticsClient({
apiBaseUrl: 'http://localhost:4005',
appName: 'test-backend',
});
client.trackApiCall({
endpoint: '/api/test',
method: 'GET',
statusCode: 200,
});
await new Promise((resolve) => setTimeout(resolve, 10));
// Should have been called with timeout signal
expect(fetchMock).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
signal: expect.any(AbortSignal),
}),
);
});
it('should map business event types to engagement metrics', async () => {
const client = new BackendAnalyticsClient({
apiBaseUrl: 'http://localhost:4005',
appName: 'test-backend',
});
const eventTypeMapping = {
message_sent: 'comment',
payment_processed: 'purchase',
stream_started: 'subscribe',
};
for (const [eventType, expectedMetric] of Object.entries(eventTypeMapping)) {
fetchMock.mockClear();
client.trackBusinessEvent({
userId: 'user-123',
eventType: eventType as any,
targetId: 'target-123',
});
await new Promise((resolve) => setTimeout(resolve, 10));
expect(fetchMock).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
body: expect.stringContaining(expectedMetric),
}),
);
}
});
it('should include metadata in business events', async () => {
const client = new BackendAnalyticsClient({
apiBaseUrl: 'http://localhost:4005',
appName: 'test-backend',
});
client.trackBusinessEvent({
userId: 'user-123',
eventType: 'message_sent',
targetId: 'msg-456',
metadata: {
channel: 'direct',
isEphemeral: false,
hasAttachments: true,
attachmentCount: 3,
},
});
await new Promise((resolve) => setTimeout(resolve, 10));
const callBody = JSON.parse(fetchMock.mock.calls[0][1].body);
expect(callBody.metadata).toEqual({
channel: 'direct',
isEphemeral: false,
hasAttachments: true,
attachmentCount: 3,
businessEventType: 'message_sent',
});
});
it('should work with minimal configuration', async () => {
const client = new BackendAnalyticsClient({
apiBaseUrl: 'http://localhost:4005',
appName: 'test-backend',
});
// Should use defaults
client.trackApiCall({
endpoint: '/api/test',
method: 'GET',
statusCode: 200,
});
await new Promise((resolve) => setTimeout(resolve, 10));
expect(fetchMock).toHaveBeenCalled();
});
});