first
This commit is contained in:
377
test/test-runner.js
Normal file
377
test/test-runner.js
Normal file
@@ -0,0 +1,377 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* @fileoverview Test runner for Laravel Healthcare MCP Server
|
||||
* Validates configuration, authentication, and basic functionality
|
||||
*/
|
||||
|
||||
import { ConfigManager } from "../src/config/ConfigManager.js";
|
||||
import { AuthManager } from "../src/auth/AuthManager.js";
|
||||
import { ApiClient } from "../src/proxy/ApiClient.js";
|
||||
import { ToolGenerator } from "../src/tools/ToolGenerator.js";
|
||||
import { logger } from "../src/utils/logger.js";
|
||||
import { AUTH_TYPES } from "../src/config/endpoints.js";
|
||||
|
||||
/**
|
||||
* Test runner class
|
||||
*/
|
||||
class TestRunner {
|
||||
constructor() {
|
||||
this.tests = [];
|
||||
this.results = {
|
||||
passed: 0,
|
||||
failed: 0,
|
||||
skipped: 0,
|
||||
total: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a test
|
||||
* @param {string} name - Test name
|
||||
* @param {Function} testFn - Test function
|
||||
* @param {boolean} skip - Skip this test
|
||||
*/
|
||||
addTest(name, testFn, skip = false) {
|
||||
this.tests.push({ name, testFn, skip });
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all tests
|
||||
*/
|
||||
async runTests() {
|
||||
console.log("🧪 Laravel Healthcare MCP Server Test Suite\n");
|
||||
|
||||
for (const test of this.tests) {
|
||||
this.results.total++;
|
||||
|
||||
if (test.skip) {
|
||||
console.log(`⏭️ SKIP: ${test.name}`);
|
||||
this.results.skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`🔄 Running: ${test.name}`);
|
||||
await test.testFn();
|
||||
console.log(`✅ PASS: ${test.name}\n`);
|
||||
this.results.passed++;
|
||||
} catch (error) {
|
||||
console.log(`❌ FAIL: ${test.name}`);
|
||||
console.log(` Error: ${error.message}\n`);
|
||||
this.results.failed++;
|
||||
}
|
||||
}
|
||||
|
||||
this.printSummary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Print test summary
|
||||
*/
|
||||
printSummary() {
|
||||
console.log("📊 Test Results Summary");
|
||||
console.log("========================");
|
||||
console.log(`Total Tests: ${this.results.total}`);
|
||||
console.log(`Passed: ${this.results.passed}`);
|
||||
console.log(`Failed: ${this.results.failed}`);
|
||||
console.log(`Skipped: ${this.results.skipped}`);
|
||||
|
||||
const successRate =
|
||||
this.results.total > 0
|
||||
? ((this.results.passed / this.results.total) * 100).toFixed(1)
|
||||
: 0;
|
||||
console.log(`Success Rate: ${successRate}%`);
|
||||
|
||||
if (this.results.failed > 0) {
|
||||
console.log(
|
||||
"\n❌ Some tests failed. Check the output above for details."
|
||||
);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log("\n✅ All tests passed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration tests
|
||||
*/
|
||||
async function testConfiguration() {
|
||||
const config = new ConfigManager();
|
||||
|
||||
// Test required configuration
|
||||
if (!config.get("LARAVEL_API_BASE_URL")) {
|
||||
throw new Error("LARAVEL_API_BASE_URL is required");
|
||||
}
|
||||
|
||||
// Test URL format
|
||||
try {
|
||||
new URL(config.get("LARAVEL_API_BASE_URL"));
|
||||
} catch (error) {
|
||||
throw new Error("LARAVEL_API_BASE_URL must be a valid URL");
|
||||
}
|
||||
|
||||
// Test numeric values
|
||||
const numericConfigs = [
|
||||
"LARAVEL_API_TIMEOUT",
|
||||
"LARAVEL_API_RETRY_ATTEMPTS",
|
||||
"TOKEN_CACHE_DURATION",
|
||||
];
|
||||
|
||||
numericConfigs.forEach((key) => {
|
||||
const value = config.get(key);
|
||||
if (value !== undefined && (isNaN(value) || value < 0)) {
|
||||
throw new Error(`${key} must be a positive number`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(" ✓ Configuration validation passed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentication manager tests
|
||||
*/
|
||||
async function testAuthManager() {
|
||||
const config = new ConfigManager();
|
||||
const authManager = new AuthManager(null, config.getAll(true));
|
||||
|
||||
// Test cache functionality
|
||||
const stats = authManager.getCacheStats();
|
||||
if (!stats || typeof stats !== "object") {
|
||||
throw new Error("Auth manager cache stats not available");
|
||||
}
|
||||
|
||||
// Test credential loading
|
||||
const credentials = authManager.credentials;
|
||||
if (!credentials || typeof credentials !== "object") {
|
||||
throw new Error("Auth manager credentials not loaded");
|
||||
}
|
||||
|
||||
// Check if at least one auth type is configured
|
||||
const configuredAuthTypes = Object.values(AUTH_TYPES).filter((authType) => {
|
||||
if (authType === AUTH_TYPES.PUBLIC) return false;
|
||||
const creds = credentials[authType];
|
||||
return creds && creds.username && creds.password;
|
||||
});
|
||||
|
||||
if (configuredAuthTypes.length === 0) {
|
||||
throw new Error("At least one authentication type must be configured");
|
||||
}
|
||||
|
||||
console.log(
|
||||
` ✓ Auth manager initialized with ${configuredAuthTypes.length} auth types`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* API client tests
|
||||
*/
|
||||
async function testApiClient() {
|
||||
const config = new ConfigManager();
|
||||
const authManager = new AuthManager(null, config.getAll(true));
|
||||
const apiClient = new ApiClient(config.getAll(), authManager);
|
||||
|
||||
// Test health status
|
||||
const health = apiClient.getHealthStatus();
|
||||
if (!health || !health.baseURL) {
|
||||
throw new Error("API client health status not available");
|
||||
}
|
||||
|
||||
// Test configuration
|
||||
if (health.baseURL !== config.get("LARAVEL_API_BASE_URL")) {
|
||||
throw new Error("API client base URL mismatch");
|
||||
}
|
||||
|
||||
console.log(" ✓ API client initialized successfully");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool generator tests
|
||||
*/
|
||||
async function testToolGenerator() {
|
||||
const config = new ConfigManager();
|
||||
const authManager = new AuthManager(null, config.getAll(true));
|
||||
const apiClient = new ApiClient(config.getAll(), authManager);
|
||||
const toolGenerator = new ToolGenerator(apiClient);
|
||||
|
||||
// Generate tools
|
||||
const tools = toolGenerator.generateAllTools();
|
||||
|
||||
if (!Array.isArray(tools) || tools.length === 0) {
|
||||
throw new Error("Tool generator did not generate any tools");
|
||||
}
|
||||
|
||||
// Test tool structure
|
||||
const firstTool = tools[0];
|
||||
const requiredFields = ["name", "description", "inputSchema"];
|
||||
|
||||
requiredFields.forEach((field) => {
|
||||
if (!firstTool[field]) {
|
||||
throw new Error(`Tool missing required field: ${field}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Test tool retrieval
|
||||
const toolByName = toolGenerator.getTool(firstTool.name);
|
||||
if (!toolByName) {
|
||||
throw new Error("Tool retrieval by name failed");
|
||||
}
|
||||
|
||||
console.log(` ✓ Tool generator created ${tools.length} tools`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint registry tests
|
||||
*/
|
||||
async function testEndpointRegistry() {
|
||||
const { PUBLIC_ENDPOINTS, AUTH_TYPES, ENDPOINT_CATEGORIES } = await import(
|
||||
"../src/config/endpoints.js"
|
||||
);
|
||||
|
||||
// Test public endpoints
|
||||
if (!Array.isArray(PUBLIC_ENDPOINTS) || PUBLIC_ENDPOINTS.length === 0) {
|
||||
throw new Error("No public endpoints defined");
|
||||
}
|
||||
|
||||
// Test endpoint structure
|
||||
const firstEndpoint = PUBLIC_ENDPOINTS[0];
|
||||
const requiredFields = [
|
||||
"path",
|
||||
"method",
|
||||
"controller",
|
||||
"category",
|
||||
"description",
|
||||
];
|
||||
|
||||
requiredFields.forEach((field) => {
|
||||
if (!firstEndpoint[field]) {
|
||||
throw new Error(`Endpoint missing required field: ${field}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Test auth types
|
||||
const authTypeValues = Object.values(AUTH_TYPES);
|
||||
if (authTypeValues.length < 8) {
|
||||
throw new Error("Not all authentication types defined");
|
||||
}
|
||||
|
||||
// Test categories
|
||||
const categoryValues = Object.values(ENDPOINT_CATEGORIES);
|
||||
if (categoryValues.length === 0) {
|
||||
throw new Error("No endpoint categories defined");
|
||||
}
|
||||
|
||||
console.log(
|
||||
` ✓ Endpoint registry has ${PUBLIC_ENDPOINTS.length} public endpoints`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Environment variable tests
|
||||
*/
|
||||
async function testEnvironmentVariables() {
|
||||
const requiredEnvVars = ["LARAVEL_API_BASE_URL"];
|
||||
const missingVars = [];
|
||||
|
||||
requiredEnvVars.forEach((varName) => {
|
||||
if (!process.env[varName]) {
|
||||
missingVars.push(varName);
|
||||
}
|
||||
});
|
||||
|
||||
if (missingVars.length > 0) {
|
||||
throw new Error(
|
||||
`Missing required environment variables: ${missingVars.join(", ")}`
|
||||
);
|
||||
}
|
||||
|
||||
// Check for at least one auth configuration
|
||||
const authTypes = [
|
||||
"ADMIN",
|
||||
"AGENT",
|
||||
"PATIENT",
|
||||
"PRACTITIONER",
|
||||
"AFFILIATE",
|
||||
"PARTNER",
|
||||
"NETWORK",
|
||||
"DOCTOR",
|
||||
"PROVIDER",
|
||||
];
|
||||
const configuredAuth = authTypes.filter((authType) => {
|
||||
return (
|
||||
process.env[`${authType}_USERNAME`] && process.env[`${authType}_PASSWORD`]
|
||||
);
|
||||
});
|
||||
|
||||
if (configuredAuth.length === 0) {
|
||||
console.log(" ⚠️ Warning: No authentication credentials configured");
|
||||
}
|
||||
|
||||
console.log(
|
||||
` ✓ Environment variables validated (${configuredAuth.length} auth types configured)`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Network connectivity tests
|
||||
*/
|
||||
async function testNetworkConnectivity() {
|
||||
const config = new ConfigManager();
|
||||
const baseUrl = config.get("LARAVEL_API_BASE_URL");
|
||||
|
||||
try {
|
||||
// Simple connectivity test using fetch
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
||||
|
||||
const response = await fetch(baseUrl, {
|
||||
method: "HEAD",
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
console.log(
|
||||
` ✓ Network connectivity to ${baseUrl} successful (${response.status})`
|
||||
);
|
||||
} catch (error) {
|
||||
if (error.name === "AbortError") {
|
||||
throw new Error(`Network timeout connecting to ${baseUrl}`);
|
||||
}
|
||||
throw new Error(`Network connectivity failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main test execution
|
||||
*/
|
||||
async function main() {
|
||||
const runner = new TestRunner();
|
||||
|
||||
// Add tests
|
||||
runner.addTest("Environment Variables", testEnvironmentVariables);
|
||||
runner.addTest("Configuration Loading", testConfiguration);
|
||||
runner.addTest("Endpoint Registry", testEndpointRegistry);
|
||||
runner.addTest("Authentication Manager", testAuthManager);
|
||||
runner.addTest("API Client", testApiClient);
|
||||
runner.addTest("Tool Generator", testToolGenerator);
|
||||
runner.addTest(
|
||||
"Network Connectivity",
|
||||
testNetworkConnectivity,
|
||||
!process.env.LARAVEL_API_BASE_URL
|
||||
);
|
||||
|
||||
// Run tests
|
||||
await runner.runTests();
|
||||
}
|
||||
|
||||
// Run tests if this file is executed directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
main().catch((error) => {
|
||||
console.error("Test runner failed:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export { TestRunner };
|
Reference in New Issue
Block a user