You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

305 lines
6.8 KiB

/**
* 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<T> {
data?: T;
error?: string;
status: number;
etag?: string;
fromCache: boolean;
}
export class TestAPIClient {
private config: TestAPIConfig;
private etagCache = new Map<string, string>();
constructor(config: TestAPIConfig) {
this.config = config;
}
/**
* Fetch notification content for a specific slot
* @param slotId - Slot identifier (e.g., 'slot-08:00')
* @returns Promise<APIResponse<NotificationContent>>
*/
async fetchContent(slotId: string): Promise<APIResponse<NotificationContent>> {
const url = `${this.config.baseUrl}/api/content/${slotId}`;
const headers: Record<string, string> = {};
// 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<APIResponse<any>>
*/
async testError(errorType: string): Promise<APIResponse<any>> {
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<APIResponse<any>>
*/
async getHealth(): Promise<APIResponse<any>> {
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<APIResponse<any>>
*/
async getMetrics(): Promise<APIResponse<any>> {
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<string, string> {
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);
}
}
};