Add API and Deployment documentation for WiFi-DensePose

- Created comprehensive API reference documentation covering authentication, request/response formats, error handling, and various API endpoints for pose estimation, system management, health checks, and WebSocket interactions.
- Developed a detailed deployment guide outlining prerequisites, Docker and Kubernetes deployment steps, cloud deployment options for AWS, GCP, and Azure, and configuration for production environments.
This commit is contained in:
rUv
2025-06-07 13:33:33 +00:00
parent 6fe0d42f90
commit ccc0957fb6
6 changed files with 4110 additions and 1 deletions

228
ui/tests/test-runner.html Normal file
View File

@@ -0,0 +1,228 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WiFi DensePose UI Tests</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.test-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
border-radius: 10px;
margin-bottom: 30px;
text-align: center;
}
.test-suite {
background: white;
border-radius: 8px;
margin-bottom: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
overflow: hidden;
}
.test-suite-header {
background: #f8f9fa;
padding: 15px 20px;
border-bottom: 1px solid #dee2e6;
font-weight: bold;
color: #495057;
}
.test-case {
padding: 15px 20px;
border-bottom: 1px solid #f8f9fa;
display: flex;
justify-content: space-between;
align-items: center;
}
.test-case:last-child {
border-bottom: none;
}
.test-name {
flex: 1;
font-weight: 500;
}
.test-status {
padding: 5px 15px;
border-radius: 20px;
font-size: 12px;
font-weight: bold;
text-transform: uppercase;
}
.test-status.pass {
background: #d4edda;
color: #155724;
}
.test-status.fail {
background: #f8d7da;
color: #721c24;
}
.test-status.pending {
background: #fff3cd;
color: #856404;
}
.test-summary {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.summary-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 20px;
margin-top: 15px;
}
.stat-item {
text-align: center;
padding: 15px;
border-radius: 8px;
background: #f8f9fa;
}
.stat-number {
font-size: 24px;
font-weight: bold;
margin-bottom: 5px;
}
.stat-label {
font-size: 12px;
color: #6c757d;
text-transform: uppercase;
}
.run-tests-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 12px 30px;
border-radius: 25px;
font-weight: bold;
cursor: pointer;
transition: transform 0.2s ease;
margin-right: 10px;
}
.run-tests-btn:hover {
transform: translateY(-2px);
}
.run-tests-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.test-output {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 15px;
margin-top: 20px;
font-family: 'Courier New', monospace;
font-size: 12px;
white-space: pre-wrap;
max-height: 300px;
overflow-y: auto;
}
.controls {
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="test-header">
<h1>WiFi DensePose UI Test Suite</h1>
<p>Comprehensive testing for the modular UI components and API integration</p>
</div>
<div class="controls">
<button id="runAllTests" class="run-tests-btn">Run All Tests</button>
<button id="runUnitTests" class="run-tests-btn">Run Unit Tests</button>
<button id="runIntegrationTests" class="run-tests-btn">Run Integration Tests</button>
<button id="clearResults" class="run-tests-btn" style="background: #dc3545;">Clear Results</button>
</div>
<div class="test-summary">
<h3>Test Summary</h3>
<div class="summary-stats">
<div class="stat-item">
<div class="stat-number" id="totalTests">0</div>
<div class="stat-label">Total Tests</div>
</div>
<div class="stat-item">
<div class="stat-number" id="passedTests" style="color: #28a745;">0</div>
<div class="stat-label">Passed</div>
</div>
<div class="stat-item">
<div class="stat-number" id="failedTests" style="color: #dc3545;">0</div>
<div class="stat-label">Failed</div>
</div>
<div class="stat-item">
<div class="stat-number" id="pendingTests" style="color: #ffc107;">0</div>
<div class="stat-label">Pending</div>
</div>
</div>
</div>
<div class="test-suite">
<div class="test-suite-header">API Configuration Tests</div>
<div id="apiConfigTests"></div>
</div>
<div class="test-suite">
<div class="test-suite-header">API Service Tests</div>
<div id="apiServiceTests"></div>
</div>
<div class="test-suite">
<div class="test-suite-header">WebSocket Service Tests</div>
<div id="websocketServiceTests"></div>
</div>
<div class="test-suite">
<div class="test-suite-header">Pose Service Tests</div>
<div id="poseServiceTests"></div>
</div>
<div class="test-suite">
<div class="test-suite-header">Health Service Tests</div>
<div id="healthServiceTests"></div>
</div>
<div class="test-suite">
<div class="test-suite-header">UI Component Tests</div>
<div id="uiComponentTests"></div>
</div>
<div class="test-suite">
<div class="test-suite-header">Integration Tests</div>
<div id="integrationTests"></div>
</div>
<div class="test-output" id="testOutput" style="display: none;"></div>
<script type="module" src="test-runner.js"></script>
</body>
</html>

476
ui/tests/test-runner.js Normal file
View File

@@ -0,0 +1,476 @@
// Test Runner for WiFi DensePose UI
import { API_CONFIG, buildApiUrl, buildWsUrl } from '../config/api.config.js';
import { apiService } from '../services/api.service.js';
import { wsService } from '../services/websocket.service.js';
import { poseService } from '../services/pose.service.js';
import { healthService } from '../services/health.service.js';
import { TabManager } from '../components/TabManager.js';
class TestRunner {
constructor() {
this.tests = [];
this.results = {
total: 0,
passed: 0,
failed: 0,
pending: 0
};
this.output = [];
}
// Add a test
test(name, category, testFn) {
this.tests.push({
name,
category,
fn: testFn,
status: 'pending'
});
}
// Run all tests
async runAllTests() {
this.clearResults();
this.log('Starting test suite...\n');
for (const test of this.tests) {
await this.runSingleTest(test);
}
this.updateSummary();
this.log(`\nTest suite completed. ${this.results.passed}/${this.results.total} tests passed.`);
}
// Run tests by category
async runTestsByCategory(category) {
this.clearResults();
const categoryTests = this.tests.filter(test => test.category === category);
this.log(`Starting ${category} tests...\n`);
for (const test of categoryTests) {
await this.runSingleTest(test);
}
this.updateSummary();
this.log(`\n${category} tests completed. ${this.results.passed}/${this.results.total} tests passed.`);
}
// Run a single test
async runSingleTest(test) {
this.log(`Running: ${test.name}...`);
try {
const startTime = Date.now();
await test.fn();
const duration = Date.now() - startTime;
test.status = 'pass';
this.results.passed++;
this.log(` ✓ PASS (${duration}ms)`);
} catch (error) {
test.status = 'fail';
test.error = error.message;
this.results.failed++;
this.log(` ✗ FAIL: ${error.message}`);
} finally {
this.results.total++;
this.updateTestDisplay(test);
}
}
// Assertion helpers
assert(condition, message) {
if (!condition) {
throw new Error(message || 'Assertion failed');
}
}
assertEqual(actual, expected, message) {
if (actual !== expected) {
throw new Error(message || `Expected ${expected}, got ${actual}`);
}
}
assertNotEqual(actual, unexpected, message) {
if (actual === unexpected) {
throw new Error(message || `Expected not to equal ${unexpected}`);
}
}
assertThrows(fn, message) {
try {
fn();
throw new Error(message || 'Expected function to throw');
} catch (error) {
// Expected
}
}
async assertRejects(promise, message) {
try {
await promise;
throw new Error(message || 'Expected promise to reject');
} catch (error) {
// Expected
}
}
// Logging
log(message) {
this.output.push(message);
const outputElement = document.getElementById('testOutput');
if (outputElement) {
outputElement.style.display = 'block';
outputElement.textContent = this.output.join('\n');
outputElement.scrollTop = outputElement.scrollHeight;
}
}
// Clear results
clearResults() {
this.results = { total: 0, passed: 0, failed: 0, pending: 0 };
this.output = [];
// Reset test statuses
this.tests.forEach(test => {
test.status = 'pending';
delete test.error;
});
// Clear UI
this.updateSummary();
this.tests.forEach(test => this.updateTestDisplay(test));
const outputElement = document.getElementById('testOutput');
if (outputElement) {
outputElement.style.display = 'none';
outputElement.textContent = '';
}
}
// Update test display
updateTestDisplay(test) {
const container = document.getElementById(`${test.category}Tests`);
if (!container) return;
let testElement = container.querySelector(`[data-test="${test.name}"]`);
if (!testElement) {
testElement = document.createElement('div');
testElement.className = 'test-case';
testElement.setAttribute('data-test', test.name);
testElement.innerHTML = `
<div class="test-name">${test.name}</div>
<div class="test-status pending">pending</div>
`;
container.appendChild(testElement);
}
const statusElement = testElement.querySelector('.test-status');
statusElement.className = `test-status ${test.status}`;
statusElement.textContent = test.status;
if (test.error) {
statusElement.title = test.error;
}
}
// Update summary
updateSummary() {
document.getElementById('totalTests').textContent = this.results.total;
document.getElementById('passedTests').textContent = this.results.passed;
document.getElementById('failedTests').textContent = this.results.failed;
document.getElementById('pendingTests').textContent = this.tests.length - this.results.total;
}
}
// Create test runner instance
const testRunner = new TestRunner();
// Mock DOM elements for testing
function createMockContainer() {
const container = document.createElement('div');
container.innerHTML = `
<nav class="nav-tabs">
<button class="nav-tab active" data-tab="dashboard">Dashboard</button>
<button class="nav-tab" data-tab="hardware">Hardware</button>
</nav>
<div id="dashboard" class="tab-content active"></div>
<div id="hardware" class="tab-content"></div>
`;
return container;
}
// API Configuration Tests
testRunner.test('API_CONFIG contains required endpoints', 'apiConfig', () => {
testRunner.assert(API_CONFIG.ENDPOINTS, 'ENDPOINTS should exist');
testRunner.assert(API_CONFIG.ENDPOINTS.POSE, 'POSE endpoints should exist');
testRunner.assert(API_CONFIG.ENDPOINTS.HEALTH, 'HEALTH endpoints should exist');
testRunner.assert(API_CONFIG.ENDPOINTS.STREAM, 'STREAM endpoints should exist');
});
testRunner.test('buildApiUrl constructs correct URLs', 'apiConfig', () => {
const url = buildApiUrl('/api/v1/pose/current', { zone_id: 'zone1', limit: 10 });
testRunner.assert(url.includes('/api/v1/pose/current'), 'URL should contain endpoint');
testRunner.assert(url.includes('zone_id=zone1'), 'URL should contain zone_id parameter');
testRunner.assert(url.includes('limit=10'), 'URL should contain limit parameter');
});
testRunner.test('buildApiUrl handles path parameters', 'apiConfig', () => {
const url = buildApiUrl('/api/v1/pose/zones/{zone_id}/occupancy', { zone_id: 'zone1' });
testRunner.assert(url.includes('/api/v1/pose/zones/zone1/occupancy'), 'URL should replace path parameter');
testRunner.assert(!url.includes('{zone_id}'), 'URL should not contain placeholder');
});
testRunner.test('buildWsUrl constructs WebSocket URLs', 'apiConfig', () => {
const url = buildWsUrl('/api/v1/stream/pose', { token: 'test-token' });
testRunner.assert(url.startsWith('ws://') || url.startsWith('wss://'), 'URL should be WebSocket protocol');
testRunner.assert(url.includes('/api/v1/stream/pose'), 'URL should contain endpoint');
testRunner.assert(url.includes('token=test-token'), 'URL should contain token parameter');
});
// API Service Tests
testRunner.test('apiService has required methods', 'apiService', () => {
testRunner.assert(typeof apiService.get === 'function', 'get method should exist');
testRunner.assert(typeof apiService.post === 'function', 'post method should exist');
testRunner.assert(typeof apiService.put === 'function', 'put method should exist');
testRunner.assert(typeof apiService.delete === 'function', 'delete method should exist');
});
testRunner.test('apiService can set auth token', 'apiService', () => {
const token = 'test-token-123';
apiService.setAuthToken(token);
testRunner.assertEqual(apiService.authToken, token, 'Auth token should be set');
});
testRunner.test('apiService builds correct headers', 'apiService', () => {
apiService.setAuthToken('test-token');
const headers = apiService.getHeaders();
testRunner.assert(headers['Content-Type'], 'Content-Type header should exist');
testRunner.assert(headers['Authorization'], 'Authorization header should exist');
testRunner.assertEqual(headers['Authorization'], 'Bearer test-token', 'Authorization header should be correct');
});
testRunner.test('apiService handles interceptors', 'apiService', () => {
let requestIntercepted = false;
let responseIntercepted = false;
apiService.addRequestInterceptor(() => {
requestIntercepted = true;
return { url: 'test', options: {} };
});
apiService.addResponseInterceptor(() => {
responseIntercepted = true;
return new Response('{}');
});
testRunner.assert(apiService.requestInterceptors.length > 0, 'Request interceptor should be added');
testRunner.assert(apiService.responseInterceptors.length > 0, 'Response interceptor should be added');
});
// WebSocket Service Tests
testRunner.test('wsService has required methods', 'websocketService', () => {
testRunner.assert(typeof wsService.connect === 'function', 'connect method should exist');
testRunner.assert(typeof wsService.disconnect === 'function', 'disconnect method should exist');
testRunner.assert(typeof wsService.send === 'function', 'send method should exist');
testRunner.assert(typeof wsService.onMessage === 'function', 'onMessage method should exist');
});
testRunner.test('wsService generates unique connection IDs', 'websocketService', () => {
const id1 = wsService.generateId();
const id2 = wsService.generateId();
testRunner.assertNotEqual(id1, id2, 'Connection IDs should be unique');
testRunner.assert(id1.startsWith('ws_'), 'Connection ID should have correct prefix');
});
testRunner.test('wsService manages connection state', 'websocketService', () => {
const initialConnections = wsService.getActiveConnections();
testRunner.assert(Array.isArray(initialConnections), 'Active connections should be an array');
});
// Pose Service Tests
testRunner.test('poseService has required methods', 'poseService', () => {
testRunner.assert(typeof poseService.getCurrentPose === 'function', 'getCurrentPose method should exist');
testRunner.assert(typeof poseService.getZoneOccupancy === 'function', 'getZoneOccupancy method should exist');
testRunner.assert(typeof poseService.startPoseStream === 'function', 'startPoseStream method should exist');
testRunner.assert(typeof poseService.subscribeToPoseUpdates === 'function', 'subscribeToPoseUpdates method should exist');
});
testRunner.test('poseService subscription management', 'poseService', () => {
let callbackCalled = false;
const unsubscribe = poseService.subscribeToPoseUpdates(() => {
callbackCalled = true;
});
testRunner.assert(typeof unsubscribe === 'function', 'Subscribe should return unsubscribe function');
testRunner.assert(poseService.poseSubscribers.length > 0, 'Subscriber should be added');
unsubscribe();
testRunner.assertEqual(poseService.poseSubscribers.length, 0, 'Subscriber should be removed');
});
testRunner.test('poseService handles pose updates', 'poseService', () => {
let receivedUpdate = null;
poseService.subscribeToPoseUpdates(update => {
receivedUpdate = update;
});
const testUpdate = { type: 'pose_update', data: { persons: [] } };
poseService.notifyPoseSubscribers(testUpdate);
testRunner.assertEqual(receivedUpdate, testUpdate, 'Update should be received by subscriber');
});
// Health Service Tests
testRunner.test('healthService has required methods', 'healthService', () => {
testRunner.assert(typeof healthService.getSystemHealth === 'function', 'getSystemHealth method should exist');
testRunner.assert(typeof healthService.checkReadiness === 'function', 'checkReadiness method should exist');
testRunner.assert(typeof healthService.startHealthMonitoring === 'function', 'startHealthMonitoring method should exist');
testRunner.assert(typeof healthService.subscribeToHealth === 'function', 'subscribeToHealth method should exist');
});
testRunner.test('healthService subscription management', 'healthService', () => {
let callbackCalled = false;
const unsubscribe = healthService.subscribeToHealth(() => {
callbackCalled = true;
});
testRunner.assert(typeof unsubscribe === 'function', 'Subscribe should return unsubscribe function');
testRunner.assert(healthService.healthSubscribers.length > 0, 'Subscriber should be added');
unsubscribe();
testRunner.assertEqual(healthService.healthSubscribers.length, 0, 'Subscriber should be removed');
});
testRunner.test('healthService status checking', 'healthService', () => {
// Set mock health status
healthService.lastHealthStatus = { status: 'healthy' };
testRunner.assert(healthService.isSystemHealthy(), 'System should be healthy');
healthService.lastHealthStatus = { status: 'unhealthy' };
testRunner.assert(!healthService.isSystemHealthy(), 'System should not be healthy');
healthService.lastHealthStatus = null;
testRunner.assertEqual(healthService.isSystemHealthy(), null, 'System health should be null when no status');
});
// UI Component Tests
testRunner.test('TabManager can be instantiated', 'uiComponent', () => {
const container = createMockContainer();
const tabManager = new TabManager(container);
testRunner.assert(tabManager instanceof TabManager, 'TabManager should be instantiated');
});
testRunner.test('TabManager initializes tabs', 'uiComponent', () => {
const container = createMockContainer();
const tabManager = new TabManager(container);
tabManager.init();
testRunner.assert(tabManager.tabs.length > 0, 'Tabs should be found');
testRunner.assert(tabManager.tabContents.length > 0, 'Tab contents should be found');
});
testRunner.test('TabManager handles tab switching', 'uiComponent', () => {
const container = createMockContainer();
const tabManager = new TabManager(container);
tabManager.init();
let tabChangeEvent = null;
tabManager.onTabChange((newTab, oldTab) => {
tabChangeEvent = { newTab, oldTab };
});
// Switch to hardware tab
const hardwareTab = container.querySelector('[data-tab="hardware"]');
tabManager.switchTab(hardwareTab);
testRunner.assertEqual(tabManager.getActiveTab(), 'hardware', 'Active tab should be updated');
testRunner.assert(tabChangeEvent, 'Tab change event should be fired');
testRunner.assertEqual(tabChangeEvent.newTab, 'hardware', 'New tab should be correct');
});
testRunner.test('TabManager can enable/disable tabs', 'uiComponent', () => {
const container = createMockContainer();
const tabManager = new TabManager(container);
tabManager.init();
tabManager.setTabEnabled('hardware', false);
const hardwareTab = container.querySelector('[data-tab="hardware"]');
testRunner.assert(hardwareTab.disabled, 'Tab should be disabled');
testRunner.assert(hardwareTab.classList.contains('disabled'), 'Tab should have disabled class');
});
testRunner.test('TabManager can show/hide tabs', 'uiComponent', () => {
const container = createMockContainer();
const tabManager = new TabManager(container);
tabManager.init();
tabManager.setTabVisible('hardware', false);
const hardwareTab = container.querySelector('[data-tab="hardware"]');
testRunner.assertEqual(hardwareTab.style.display, 'none', 'Tab should be hidden');
});
// Integration Tests
testRunner.test('Services can be imported together', 'integration', () => {
testRunner.assert(apiService, 'API service should be available');
testRunner.assert(wsService, 'WebSocket service should be available');
testRunner.assert(poseService, 'Pose service should be available');
testRunner.assert(healthService, 'Health service should be available');
});
testRunner.test('Services maintain separate state', 'integration', () => {
// Set different states
apiService.setAuthToken('api-token');
poseService.subscribeToPoseUpdates(() => {});
healthService.subscribeToHealth(() => {});
// Verify independence
testRunner.assertEqual(apiService.authToken, 'api-token', 'API service should maintain its token');
testRunner.assert(poseService.poseSubscribers.length > 0, 'Pose service should have subscribers');
testRunner.assert(healthService.healthSubscribers.length > 0, 'Health service should have subscribers');
});
testRunner.test('Configuration is consistent across services', 'integration', () => {
// All services should use the same configuration
testRunner.assert(API_CONFIG.BASE_URL, 'Base URL should be configured');
testRunner.assert(API_CONFIG.ENDPOINTS, 'Endpoints should be configured');
testRunner.assert(API_CONFIG.WS_CONFIG, 'WebSocket config should be available');
});
// Event listeners for UI
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('runAllTests').addEventListener('click', () => {
testRunner.runAllTests();
});
document.getElementById('runUnitTests').addEventListener('click', () => {
const unitCategories = ['apiConfig', 'apiService', 'websocketService', 'poseService', 'healthService', 'uiComponent'];
testRunner.clearResults();
(async () => {
for (const category of unitCategories) {
await testRunner.runTestsByCategory(category);
}
testRunner.updateSummary();
})();
});
document.getElementById('runIntegrationTests').addEventListener('click', () => {
testRunner.runTestsByCategory('integration');
});
document.getElementById('clearResults').addEventListener('click', () => {
testRunner.clearResults();
});
// Initialize test display
testRunner.tests.forEach(test => testRunner.updateTestDisplay(test));
testRunner.updateSummary();
});
export { testRunner };