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
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);
|
|
}
|
|
}
|
|
};
|
|
|