288 lines
10 KiB
JavaScript
288 lines
10 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* Comprehensive API Endpoint Test Suite
|
||
* Tests all endpoints in unified-server.js
|
||
*/
|
||
|
||
const axios = require('axios');
|
||
|
||
const BASE_URL = 'http://localhost:4031';
|
||
const AUTH = { username: 'toemrer', password: process.env.TEST_PASSWORD };
|
||
|
||
// Test results tracking
|
||
const results = {
|
||
total: 0,
|
||
passed: 0,
|
||
failed: 0,
|
||
skipped: 0,
|
||
errors: []
|
||
};
|
||
|
||
// Color output
|
||
const colors = {
|
||
green: '\x1b[32m',
|
||
red: '\x1b[31m',
|
||
yellow: '\x1b[33m',
|
||
blue: '\x1b[34m',
|
||
reset: '\x1b[0m'
|
||
};
|
||
|
||
function log(message, color = 'reset') {
|
||
console.log(`${colors[color]}${message}${colors.reset}`);
|
||
}
|
||
|
||
async function testEndpoint(method, path, options = {}) {
|
||
results.total++;
|
||
const testName = `${method.toUpperCase()} ${path}`;
|
||
|
||
try {
|
||
const config = {
|
||
method,
|
||
url: `${BASE_URL}${path}`,
|
||
auth: AUTH,
|
||
timeout: 5000,
|
||
validateStatus: () => true, // Don't throw on any status
|
||
...options
|
||
};
|
||
|
||
const response = await axios(config);
|
||
|
||
if (response.status >= 200 && response.status < 300) {
|
||
results.passed++;
|
||
log(`✅ ${testName} - ${response.status}`, 'green');
|
||
return { success: true, status: response.status, data: response.data };
|
||
} else if (response.status === 404) {
|
||
results.failed++;
|
||
log(`❌ ${testName} - 404 NOT FOUND (REMOVE THIS ENDPOINT)`, 'red');
|
||
results.errors.push({ endpoint: testName, error: '404 - Endpoint should be removed' });
|
||
return { success: false, status: 404 };
|
||
} else if (response.status === 400 || response.status === 422) {
|
||
results.passed++;
|
||
log(`⚠️ ${testName} - ${response.status} (Expected validation error)`, 'yellow');
|
||
return { success: true, status: response.status };
|
||
} else {
|
||
results.failed++;
|
||
log(`❌ ${testName} - ${response.status}`, 'red');
|
||
results.errors.push({ endpoint: testName, status: response.status, error: response.data });
|
||
return { success: false, status: response.status };
|
||
}
|
||
} catch (error) {
|
||
results.failed++;
|
||
log(`❌ ${testName} - ${error.message}`, 'red');
|
||
results.errors.push({ endpoint: testName, error: error.message });
|
||
return { success: false, error: error.message };
|
||
}
|
||
}
|
||
|
||
async function runTests() {
|
||
log('\n🔍 COMPREHENSIVE API ENDPOINT TEST\n', 'blue');
|
||
log('Testing all 180+ endpoints in unified-server.js\n', 'blue');
|
||
|
||
// ============================================
|
||
// HEALTH & ANALYTICS
|
||
// ============================================
|
||
log('\n📊 HEALTH & ANALYTICS ENDPOINTS', 'blue');
|
||
await testEndpoint('get', '/health');
|
||
await testEndpoint('get', '/api/health');
|
||
await testEndpoint('get', '/api/analytics/kpis');
|
||
await testEndpoint('get', '/api/analytics/employees');
|
||
await testEndpoint('get', '/api/analytics/customers');
|
||
await testEndpoint('get', '/api/analytics/materials');
|
||
await testEndpoint('get', '/api/analytics/projects');
|
||
await testEndpoint('get', '/api/analytics/search?q=test');
|
||
|
||
// ============================================
|
||
// AUTHENTICATION
|
||
// ============================================
|
||
log('\n🔐 AUTHENTICATION ENDPOINTS', 'blue');
|
||
await testEndpoint('post', '/api/auth/login', {
|
||
data: { username: 'toemrer', password: process.env.TEST_PASSWORD }
|
||
});
|
||
await testEndpoint('post', '/api/auth', {
|
||
data: { username: 'toemrer', password: process.env.TEST_PASSWORD }
|
||
});
|
||
|
||
// ============================================
|
||
// QUOTES
|
||
// ============================================
|
||
log('\n📄 QUOTES ENDPOINTS', 'blue');
|
||
await testEndpoint('get', '/api/quotes/completed');
|
||
await testEndpoint('get', '/api/quotes/openai/stats');
|
||
await testEndpoint('post', '/api/quotes/openai/stats/update', {
|
||
data: { prompt_tokens: 100, completion_tokens: 50, cost: 0.01 }
|
||
});
|
||
await testEndpoint('post', '/api/quotes/generate', {
|
||
data: {
|
||
projectId: 1,
|
||
customerName: 'Test Customer',
|
||
projectType: 'Tagkonstruktion'
|
||
}
|
||
});
|
||
await testEndpoint('post', '/api/quotes/carpenter-hours', {
|
||
data: { projectType: 'Tagkonstruktion', area: 100 }
|
||
});
|
||
|
||
// ============================================
|
||
// PRICING & MATERIALS
|
||
// ============================================
|
||
log('\n💰 PRICING & MATERIALS ENDPOINTS', 'blue');
|
||
await testEndpoint('get', '/api/pricing/health');
|
||
await testEndpoint('get', '/api/pricing/materials');
|
||
await testEndpoint('get', '/api/pricing/categories');
|
||
await testEndpoint('get', '/api/pricing/materials/stats');
|
||
await testEndpoint('get', '/api/pricing/search?query=spær');
|
||
await testEndpoint('get', '/api/prices/suggestions');
|
||
await testEndpoint('get', '/api/prices/template');
|
||
await testEndpoint('get', '/api/materials/dynamic');
|
||
|
||
// ============================================
|
||
// BYGMA INTEGRATION
|
||
// ============================================
|
||
log('\n🏗️ BYGMA INTEGRATION ENDPOINTS', 'blue');
|
||
await testEndpoint('get', '/api/pricing/bygma-imports');
|
||
await testEndpoint('get', '/api/pricing/bygma-prices?search=spær');
|
||
await testEndpoint('get', '/api/pricing/bygma-stats');
|
||
await testEndpoint('get', '/api/material-categories/bygma');
|
||
|
||
// ============================================
|
||
// WEB PRICES
|
||
// ============================================
|
||
log('\n🌐 WEB PRICES ENDPOINTS', 'blue');
|
||
await testEndpoint('post', '/api/web-prices/search', {
|
||
data: { query: 'spær', limit: 10 }
|
||
});
|
||
await testEndpoint('post', '/api/web-prices/carpenter-rates', {
|
||
data: { projectType: 'Tagkonstruktion' }
|
||
});
|
||
await testEndpoint('get', '/api/web-prices/suggestions/roof');
|
||
|
||
// ============================================
|
||
// CATEGORIES
|
||
// ============================================
|
||
log('\n📁 CATEGORIES ENDPOINTS', 'blue');
|
||
await testEndpoint('get', '/api/categories');
|
||
await testEndpoint('get', '/api/categories/list');
|
||
|
||
// ============================================
|
||
// MATERIAL PACKAGES
|
||
// ============================================
|
||
log('\n📦 MATERIAL PACKAGES ENDPOINTS', 'blue');
|
||
await testEndpoint('get', '/api/material-packages');
|
||
await testEndpoint('post', '/api/material-packages', {
|
||
data: {
|
||
name: 'Test Package',
|
||
description: 'Test',
|
||
materials: []
|
||
}
|
||
});
|
||
|
||
// ============================================
|
||
// CUSTOMER PROJECTS
|
||
// ============================================
|
||
log('\n🏠 CUSTOMER PROJECTS ENDPOINTS', 'blue');
|
||
await testEndpoint('get', '/api/customer-projects/projects');
|
||
await testEndpoint('get', '/api/customer-projects/similar?projectType=Tagkonstruktion');
|
||
await testEndpoint('get', '/api/customer-projects/material-categories');
|
||
await testEndpoint('get', '/api/customer-projects/tag-experience-suggestions');
|
||
await testEndpoint('get', '/api/tag-geometry-estimates');
|
||
await testEndpoint('get', '/api/real-project-types');
|
||
await testEndpoint('post', '/api/real-project-suggestions', {
|
||
data: { projectType: 'Tagkonstruktion', area: 100 }
|
||
});
|
||
|
||
// Test creating a project
|
||
const createResult = await testEndpoint('post', '/api/customer-projects/projects', {
|
||
data: {
|
||
customer_name: 'API Test Customer',
|
||
project_type: 'Tagkonstruktion',
|
||
project_description: 'Test project for API validation'
|
||
}
|
||
});
|
||
|
||
if (createResult.success && createResult.data && createResult.data.projectId) {
|
||
const projectId = createResult.data.projectId;
|
||
log(`\n🔍 Testing project-specific endpoints for project ${projectId}`, 'blue');
|
||
|
||
await testEndpoint('get', `/api/customer-projects/${projectId}`);
|
||
await testEndpoint('get', `/api/customer-projects/${projectId}/breakdown`);
|
||
await testEndpoint('get', `/api/customer-projects/${projectId}/quotes`);
|
||
await testEndpoint('get', `/api/customer-projects/${projectId}/geometry`);
|
||
await testEndpoint('get', `/api/customer-projects/${projectId}/labor`);
|
||
await testEndpoint('get', `/api/customer-projects/${projectId}/materials`);
|
||
await testEndpoint('get', `/api/customer-projects/${projectId}/packages`);
|
||
await testEndpoint('get', `/api/customer-projects/${projectId}/calculation`);
|
||
await testEndpoint('get', `/api/customer-projects/${projectId}/tasks`);
|
||
|
||
// Update geometry
|
||
await testEndpoint('post', `/api/customer-projects/${projectId}/geometry`, {
|
||
data: {
|
||
area_m2: 100,
|
||
vindskede_lbm: 50
|
||
}
|
||
});
|
||
|
||
// Update project
|
||
await testEndpoint('put', `/api/customer-projects/${projectId}`, {
|
||
data: {
|
||
project_description: 'Updated description'
|
||
}
|
||
});
|
||
|
||
// Clean up: delete test project
|
||
await testEndpoint('delete', `/api/customer-projects/${projectId}`);
|
||
}
|
||
|
||
// ============================================
|
||
// INSTALLATION MANUALS
|
||
// ============================================
|
||
log('\n📖 INSTALLATION MANUALS ENDPOINTS', 'blue');
|
||
await testEndpoint('get', '/api/customer/installation-manual/search?product_name=280');
|
||
|
||
// ============================================
|
||
// ORDERS (if implemented)
|
||
// ============================================
|
||
log('\n📋 ORDERS ENDPOINTS', 'blue');
|
||
await testEndpoint('get', '/api/orders');
|
||
await testEndpoint('get', '/api/orders/status-summary');
|
||
|
||
// ============================================
|
||
// UPLOADS
|
||
// ============================================
|
||
log('\n📤 UPLOADS ENDPOINTS', 'blue');
|
||
await testEndpoint('get', '/api/uploads/recent');
|
||
|
||
// ============================================
|
||
// SUMMARY
|
||
// ============================================
|
||
log('\n' + '='.repeat(60), 'blue');
|
||
log('📊 TEST SUMMARY', 'blue');
|
||
log('='.repeat(60), 'blue');
|
||
log(`Total Tests: ${results.total}`);
|
||
log(`✅ Passed: ${results.passed}`, 'green');
|
||
log(`❌ Failed: ${results.failed}`, results.failed > 0 ? 'red' : 'green');
|
||
log(`⏭️ Skipped: ${results.skipped}`, 'yellow');
|
||
|
||
const successRate = ((results.passed / results.total) * 100).toFixed(1);
|
||
log(`\n📈 Success Rate: ${successRate}%`, successRate > 90 ? 'green' : 'yellow');
|
||
|
||
if (results.errors.length > 0) {
|
||
log('\n❌ ERRORS FOUND:', 'red');
|
||
results.errors.forEach((error, index) => {
|
||
log(`${index + 1}. ${error.endpoint}`, 'red');
|
||
log(` ${JSON.stringify(error.error || error.status)}`, 'red');
|
||
});
|
||
}
|
||
|
||
log('\n✨ Test completed!\n', 'blue');
|
||
|
||
// Exit with error code if tests failed
|
||
process.exit(results.failed > 0 ? 1 : 0);
|
||
}
|
||
|
||
// Run tests
|
||
runTests().catch(error => {
|
||
log(`\n💥 Fatal error: ${error.message}`, 'red');
|
||
console.error(error);
|
||
process.exit(1);
|
||
});
|