/** * Test API Client for Daily Notification Plugin * * Demonstrates how to integrate with the test API server * for validating plugin functionality. * * @author Matthew Raymer * @version 1.0.0 */ export interface TestAPIConfig { baseUrl: string; timeout: number; } export interface NotificationContent { id: string; slotId: string; title: string; body: string; timestamp: number; priority: string; category: string; actions: Array<{ id: string; title: string }>; metadata: { source: string; version: string; generated: string; }; } export interface APIResponse { data?: T; error?: string; status: number; etag?: string; fromCache: boolean; } export class TestAPIClient { private config: TestAPIConfig; private etagCache = new Map(); constructor(config: TestAPIConfig) { this.config = config; } /** * Fetch notification content for a specific slot * @param slotId - Slot identifier (e.g., 'slot-08:00') * @returns Promise> */ async fetchContent(slotId: string): Promise> { const url = `${this.config.baseUrl}/api/content/${slotId}`; const headers: Record = {}; // Add ETag for conditional request if we have cached content const cachedETag = this.etagCache.get(slotId); if (cachedETag) { headers['If-None-Match'] = cachedETag; } try { const response = await fetch(url, { method: 'GET', headers, signal: AbortSignal.timeout(this.config.timeout) }); const etag = response.headers.get('ETag'); const fromCache = response.status === 304; if (fromCache) { return { status: response.status, fromCache: true, etag: cachedETag }; } if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); // Cache ETag for future conditional requests if (etag) { this.etagCache.set(slotId, etag); } return { data, status: response.status, etag, fromCache: false }; } catch (error) { return { error: error instanceof Error ? error.message : 'Unknown error', status: 0, fromCache: false }; } } /** * Test error scenarios * @param errorType - Type of error to simulate * @returns Promise> */ async testError(errorType: string): Promise> { const url = `${this.config.baseUrl}/api/error/${errorType}`; try { const response = await fetch(url, { method: 'GET', signal: AbortSignal.timeout(this.config.timeout) }); const data = await response.json(); return { data, status: response.status, fromCache: false }; } catch (error) { return { error: error instanceof Error ? error.message : 'Unknown error', status: 0, fromCache: false }; } } /** * Get API health status * @returns Promise> */ async getHealth(): Promise> { const url = `${this.config.baseUrl}/health`; try { const response = await fetch(url, { method: 'GET', signal: AbortSignal.timeout(this.config.timeout) }); const data = await response.json(); return { data, status: response.status, fromCache: false }; } catch (error) { return { error: error instanceof Error ? error.message : 'Unknown error', status: 0, fromCache: false }; } } /** * Get API metrics * @returns Promise> */ async getMetrics(): Promise> { const url = `${this.config.baseUrl}/api/metrics`; try { const response = await fetch(url, { method: 'GET', signal: AbortSignal.timeout(this.config.timeout) }); const data = await response.json(); return { data, status: response.status, fromCache: false }; } catch (error) { return { error: error instanceof Error ? error.message : 'Unknown error', status: 0, fromCache: false }; } } /** * Clear ETag cache */ clearCache(): void { this.etagCache.clear(); } /** * Get cached ETags * @returns Map of slotId to ETag */ getCachedETags(): Map { return new Map(this.etagCache); } } /** * Platform-specific API configuration */ export const getAPIConfig = (): TestAPIConfig => { // Detect platform and set appropriate base URL if (typeof window !== 'undefined') { // Web/Electron return { baseUrl: 'http://localhost:3001', timeout: 12000 // 12 seconds }; } // Default configuration return { baseUrl: 'http://localhost:3001', timeout: 12000 }; }; /** * Usage examples for test apps */ export const TestAPIExamples = { /** * Basic content fetching example */ async basicFetch() { const client = new TestAPIClient(getAPIConfig()); console.log('Testing basic content fetch...'); const result = await client.fetchContent('slot-08:00'); if (result.error) { console.error('Error:', result.error); } else { console.log('Success:', result.data); console.log('ETag:', result.etag); console.log('From cache:', result.fromCache); } }, /** * ETag caching example */ async etagCaching() { const client = new TestAPIClient(getAPIConfig()); console.log('Testing ETag caching...'); // First request const result1 = await client.fetchContent('slot-08:00'); console.log('First request:', result1.fromCache ? 'From cache' : 'Fresh content'); // Second request (should be from cache) const result2 = await client.fetchContent('slot-08:00'); console.log('Second request:', result2.fromCache ? 'From cache' : 'Fresh content'); }, /** * Error handling example */ async errorHandling() { const client = new TestAPIClient(getAPIConfig()); console.log('Testing error handling...'); const errorTypes = ['timeout', 'server-error', 'not-found', 'rate-limit']; for (const errorType of errorTypes) { const result = await client.testError(errorType); console.log(`${errorType}:`, result.status, result.error || 'Success'); } }, /** * Health check example */ async healthCheck() { const client = new TestAPIClient(getAPIConfig()); console.log('Testing health check...'); const result = await client.getHealth(); if (result.error) { console.error('Health check failed:', result.error); } else { console.log('API is healthy:', result.data); } } };