378 lines
9.5 KiB
JavaScript
378 lines
9.5 KiB
JavaScript
#!/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 };
|