feat(error-handling): Implement comprehensive gateway error handling with user-friendly messages
- Add comprehensive gateway error handler with friendly user messages - Handle subscription status errors (inactive BYOK, subscription required) - Handle authentication errors (invalid API keys, missing tokens) - Handle rate limiting with retry suggestions - Handle model availability and validation errors - Handle network connectivity issues - Provide actionable solutions for each error type - Prevent duplicate error messages by returning early after showing friendly error - Fix telemetry tests to use correct environment variable names (TASKMASTER_API_KEY) - Fix config manager getUserId function to properly save default userId to file - All tests now passing (34 test suites, 360 tests)
This commit is contained in:
@@ -30,9 +30,9 @@
|
|||||||
"ollamaBaseUrl": "http://localhost:11434/api"
|
"ollamaBaseUrl": "http://localhost:11434/api"
|
||||||
},
|
},
|
||||||
"account": {
|
"account": {
|
||||||
"userId": "1234567890",
|
"userId": "ee196145-8f01-41b9-a9ce-85faae397254",
|
||||||
"email": "",
|
"email": "eyal@testing.com",
|
||||||
"mode": "byok",
|
"mode": "hosted",
|
||||||
"telemetryEnabled": true
|
"telemetryEnabled": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
3
package-lock.json
generated
3
package-lock.json
generated
@@ -45,7 +45,8 @@
|
|||||||
"ora": "^8.2.0",
|
"ora": "^8.2.0",
|
||||||
"task-master-ai": "^0.15.0",
|
"task-master-ai": "^0.15.0",
|
||||||
"uuid": "^11.1.0",
|
"uuid": "^11.1.0",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8",
|
||||||
|
"zod-to-json-schema": "^3.24.5"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"task-master": "bin/task-master.js",
|
"task-master": "bin/task-master.js",
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import {
|
|||||||
} from "../../src/ai-providers/index.js";
|
} from "../../src/ai-providers/index.js";
|
||||||
|
|
||||||
import { zodToJsonSchema } from "zod-to-json-schema";
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
||||||
|
import { handleGatewayError } from "./utils/gatewayErrorHandler.js";
|
||||||
|
|
||||||
// Create provider instances
|
// Create provider instances
|
||||||
const PROVIDERS = {
|
const PROVIDERS = {
|
||||||
@@ -346,31 +347,51 @@ async function _callGatewayAI(
|
|||||||
headers["X-User-Email"] = userEmail;
|
headers["X-User-Email"] = userEmail;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(endpoint, {
|
try {
|
||||||
method: "POST",
|
const response = await fetch(endpoint, {
|
||||||
headers,
|
method: "POST",
|
||||||
body: JSON.stringify(requestBody),
|
headers,
|
||||||
});
|
body: JSON.stringify(requestBody),
|
||||||
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
throw new Error(`Gateway AI call failed: ${response.status} ${errorText}`);
|
throw new Error(
|
||||||
|
`Gateway AI call failed: ${response.status} ${errorText}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || "Gateway AI call failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the AI response in the expected format
|
||||||
|
return {
|
||||||
|
text: result.data.text,
|
||||||
|
object: result.data.object,
|
||||||
|
usage: result.data.usage,
|
||||||
|
// Include any account info returned from gateway
|
||||||
|
accountInfo: result.accountInfo,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
// Use the enhanced error handler for user-friendly messages
|
||||||
|
handleGatewayError(error, commandName);
|
||||||
|
|
||||||
|
// Throw a much cleaner error message to prevent ugly double logging
|
||||||
|
const match = error.message.match(/Gateway AI call failed: (\d+)/);
|
||||||
|
if (match) {
|
||||||
|
const statusCode = match[1];
|
||||||
|
throw new Error(
|
||||||
|
`TaskMaster gateway error (${statusCode}). See details above.`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
"TaskMaster gateway communication failed. See details above."
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (!result.success) {
|
|
||||||
throw new Error(result.error || "Gateway AI call failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the AI response in the expected format
|
|
||||||
return {
|
|
||||||
text: result.data.text,
|
|
||||||
object: result.data.object,
|
|
||||||
usage: result.data.usage,
|
|
||||||
// Include any account info returned from gateway
|
|
||||||
accountInfo: result.accountInfo,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -721,12 +721,56 @@ function getUserId(explicitRoot = null) {
|
|||||||
config.account = { ...defaultConfig.account };
|
config.account = { ...defaultConfig.account };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the userId exists in the actual file (not merged config)
|
||||||
|
let needsToSaveUserId = false;
|
||||||
|
|
||||||
|
// Load the raw config to check if userId is actually in the file
|
||||||
|
try {
|
||||||
|
let rootPath = explicitRoot;
|
||||||
|
if (explicitRoot === null || explicitRoot === undefined) {
|
||||||
|
const foundRoot = findProjectRoot();
|
||||||
|
if (!foundRoot) {
|
||||||
|
// If no project root, can't check file, assume userId needs to be saved
|
||||||
|
needsToSaveUserId = true;
|
||||||
|
} else {
|
||||||
|
rootPath = foundRoot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rootPath && !needsToSaveUserId) {
|
||||||
|
const configPath = path.join(rootPath, CONFIG_FILE_NAME);
|
||||||
|
if (fs.existsSync(configPath)) {
|
||||||
|
const rawConfig = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
||||||
|
// Check if userId is missing from the actual file
|
||||||
|
needsToSaveUserId = !rawConfig.account?.userId;
|
||||||
|
} else {
|
||||||
|
// Config file doesn't exist, need to save
|
||||||
|
needsToSaveUserId = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// If there's any error reading the file, assume we need to save
|
||||||
|
needsToSaveUserId = true;
|
||||||
|
}
|
||||||
|
|
||||||
// If userId exists and is not the placeholder, return it
|
// If userId exists and is not the placeholder, return it
|
||||||
if (config.account.userId && config.account.userId !== "1234567890") {
|
if (config.account.userId && config.account.userId !== "1234567890") {
|
||||||
return config.account.userId;
|
return config.account.userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If userId is null or the placeholder, return the placeholder
|
// If userId is missing from the actual file, set the placeholder and save it
|
||||||
|
if (needsToSaveUserId) {
|
||||||
|
config.account.userId = "1234567890";
|
||||||
|
const success = writeConfig(config, explicitRoot);
|
||||||
|
if (!success) {
|
||||||
|
console.warn("Warning: Failed to save default userId to config file");
|
||||||
|
}
|
||||||
|
// Force reload the cached config to reflect the change
|
||||||
|
loadedConfig = null;
|
||||||
|
loadedConfigRoot = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the placeholder
|
||||||
// This signals to other code that auth/init needs to be attempted
|
// This signals to other code that auth/init needs to be attempted
|
||||||
return "1234567890";
|
return "1234567890";
|
||||||
}
|
}
|
||||||
|
|||||||
186
scripts/modules/utils/gatewayErrorHandler.js
Normal file
186
scripts/modules/utils/gatewayErrorHandler.js
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
/**
|
||||||
|
* Enhanced error handler for gateway responses
|
||||||
|
* @param {Error} error - The error from the gateway call
|
||||||
|
* @param {string} commandName - The command being executed
|
||||||
|
*/
|
||||||
|
function handleGatewayError(error, commandName) {
|
||||||
|
try {
|
||||||
|
// Extract status code and response from error message
|
||||||
|
const match = error.message.match(/Gateway AI call failed: (\d+) (.+)/);
|
||||||
|
if (!match) {
|
||||||
|
throw new Error(`Unexpected error format: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, statusCode, responseText] = match;
|
||||||
|
const status = parseInt(statusCode);
|
||||||
|
|
||||||
|
let response;
|
||||||
|
try {
|
||||||
|
response = JSON.parse(responseText);
|
||||||
|
} catch {
|
||||||
|
// Handle non-JSON error responses
|
||||||
|
console.error(`[ERROR] Gateway error (${status}): ${responseText}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case 400:
|
||||||
|
handleValidationError(response, commandName);
|
||||||
|
break;
|
||||||
|
case 401:
|
||||||
|
handleAuthError(response, commandName);
|
||||||
|
break;
|
||||||
|
case 402:
|
||||||
|
handleCreditError(response, commandName);
|
||||||
|
break;
|
||||||
|
case 403:
|
||||||
|
handleAccessDeniedError(response, commandName);
|
||||||
|
break;
|
||||||
|
case 429:
|
||||||
|
handleRateLimitError(response, commandName);
|
||||||
|
break;
|
||||||
|
case 500:
|
||||||
|
handleServerError(response, commandName);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.error(
|
||||||
|
`[ERROR] Unexpected gateway error (${status}):`,
|
||||||
|
response
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (parseError) {
|
||||||
|
console.error(`[ERROR] Failed to parse gateway error: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleValidationError(response, commandName) {
|
||||||
|
if (response.error?.includes("Unsupported model")) {
|
||||||
|
console.error("🚫 The selected AI model is not supported by the gateway.");
|
||||||
|
console.error(
|
||||||
|
"💡 Try running `task-master models` to see available models."
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.error?.includes("schema is required")) {
|
||||||
|
console.error("🚫 This command requires a schema for structured output.");
|
||||||
|
console.error("💡 This is likely a bug - please report it.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(`🚫 Invalid request: ${response.error}`);
|
||||||
|
if (response.details?.length > 0) {
|
||||||
|
response.details.forEach((detail) => {
|
||||||
|
console.error(` • ${detail.message || detail}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAuthError(response, commandName) {
|
||||||
|
console.error("🔐 Authentication failed with TaskMaster gateway.");
|
||||||
|
|
||||||
|
if (response.message?.includes("Invalid token")) {
|
||||||
|
console.error("💡 Your auth token may have expired. Try running:");
|
||||||
|
console.error(" task-master init");
|
||||||
|
} else if (response.message?.includes("Missing X-TaskMaster-Service-ID")) {
|
||||||
|
console.error(
|
||||||
|
"💡 Service authentication issue. This is likely a bug - please report it."
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.error("💡 Please check your authentication settings.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCreditError(response, commandName) {
|
||||||
|
console.error("💳 Insufficient credits for this operation.");
|
||||||
|
console.error(`💡 ${response.message || "Your account needs more credits."}`);
|
||||||
|
console.error(" • Visit your dashboard to add credits");
|
||||||
|
console.error(" • Or upgrade to a plan with more credits");
|
||||||
|
console.error(
|
||||||
|
" • You can also switch to BYOK mode to use your own API keys"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAccessDeniedError(response, commandName) {
|
||||||
|
const { details, hint } = response;
|
||||||
|
|
||||||
|
if (
|
||||||
|
details?.planType === "byok" &&
|
||||||
|
details?.subscriptionStatus === "inactive"
|
||||||
|
) {
|
||||||
|
console.error(
|
||||||
|
"🔒 BYOK users need active subscriptions for hosted AI services."
|
||||||
|
);
|
||||||
|
console.error("💡 You have two options:");
|
||||||
|
console.error(" 1. Upgrade to a paid plan for hosted AI services");
|
||||||
|
console.error(" 2. Switch to BYOK mode and use your own API keys");
|
||||||
|
console.error("");
|
||||||
|
console.error(" To use your own API keys:");
|
||||||
|
console.error(
|
||||||
|
" • Set your API keys in .env file (e.g., ANTHROPIC_API_KEY=...)"
|
||||||
|
);
|
||||||
|
console.error(" • The system will automatically use direct API calls");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (details?.subscriptionStatus === "past_due") {
|
||||||
|
console.error("💳 Your subscription payment is overdue.");
|
||||||
|
console.error(
|
||||||
|
"💡 Please update your payment method to continue using AI services."
|
||||||
|
);
|
||||||
|
console.error(
|
||||||
|
" Visit your account dashboard to update billing information."
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (details?.planType === "free" && commandName === "research") {
|
||||||
|
console.error("🔬 Research features require a paid subscription.");
|
||||||
|
console.error("💡 Upgrade your plan to access research-powered commands.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(`🔒 Access denied: ${response.message}`);
|
||||||
|
if (hint) {
|
||||||
|
console.error(`💡 ${hint}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRateLimitError(response, commandName) {
|
||||||
|
const retryAfter = response.retryAfter || 60;
|
||||||
|
console.error("⏱️ Rate limit exceeded - too many requests.");
|
||||||
|
console.error(`💡 Please wait ${retryAfter} seconds before trying again.`);
|
||||||
|
console.error(" Consider upgrading your plan for higher rate limits.");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleServerError(response, commandName) {
|
||||||
|
const retryAfter = response.retryAfter || 10;
|
||||||
|
|
||||||
|
if (response.error?.includes("Service temporarily unavailable")) {
|
||||||
|
console.error("🚧 TaskMaster gateway is temporarily unavailable.");
|
||||||
|
console.error(
|
||||||
|
`💡 The service should recover automatically. Try again in ${retryAfter} seconds.`
|
||||||
|
);
|
||||||
|
console.error(
|
||||||
|
" You can also switch to BYOK mode to use direct API calls."
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.message?.includes("No user message found")) {
|
||||||
|
console.error("🚫 Invalid request format - missing user message.");
|
||||||
|
console.error("💡 This is likely a bug - please report it.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error("⚠️ Gateway server error occurred.");
|
||||||
|
console.error(
|
||||||
|
`💡 Try again in ${retryAfter} seconds. If the problem persists:`
|
||||||
|
);
|
||||||
|
console.error(" • Check TaskMaster status page");
|
||||||
|
console.error(" • Switch to BYOK mode as a workaround");
|
||||||
|
console.error(" • Contact support if the issue continues");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export the main handler function
|
||||||
|
export { handleGatewayError };
|
||||||
@@ -6333,35 +6333,6 @@
|
|||||||
"status": "pending"
|
"status": "pending"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 96,
|
|
||||||
"title": "Create Test Task Generation and Validation System",
|
|
||||||
"description": "Implement a system for generating test tasks with proper validation, structure, and integration with the existing task management framework.",
|
|
||||||
"details": "Build a comprehensive test task generation system that includes:\n\n1. Test Task Template System:\n - Create standardized templates for different types of test tasks (unit, integration, e2e)\n - Implement template variables for dynamic content generation\n - Support for test-specific metadata (test type, coverage areas, execution time)\n\n2. Test Task Validation:\n - Validate test task structure against schema requirements\n - Ensure test tasks have proper dependencies on implementation tasks\n - Verify test strategy completeness and specificity\n - Check for required test metadata fields\n\n3. Integration with Task Management:\n - Extend existing task creation commands to support test task generation\n - Add test task filtering and categorization in list commands\n - Implement test task execution tracking and results storage\n - Support for test task hierarchies and grouping\n\n4. Test Task Automation:\n - Auto-generate test tasks when implementation tasks are created\n - Implement test task dependency resolution based on implementation dependencies\n - Add support for test task scheduling and execution workflows\n - Create test task reporting and metrics collection\n\n5. CLI Integration:\n - Add 'create-test-task' command with options for test type and target functionality\n - Implement test task status tracking (pending, running, passed, failed)\n - Add test task execution commands with result reporting\n - Support for bulk test task operations",
|
|
||||||
"testStrategy": "Verify the test task generation system by:\n\n1. Template Validation:\n - Create test tasks using each template type and verify proper structure\n - Test template variable substitution with various input scenarios\n - Validate generated test tasks against the task schema\n\n2. Integration Testing:\n - Test creation of test tasks through CLI commands\n - Verify test task filtering and listing functionality\n - Test dependency resolution between test tasks and implementation tasks\n - Validate test task execution workflow end-to-end\n\n3. Validation Testing:\n - Test validation rules with valid and invalid test task structures\n - Verify error handling for malformed test tasks\n - Test schema compliance for all generated test tasks\n\n4. Automation Testing:\n - Test auto-generation of test tasks when implementation tasks are created\n - Verify dependency propagation from implementation to test tasks\n - Test bulk operations on multiple test tasks\n\n5. CLI Testing:\n - Test all new CLI commands with various parameter combinations\n - Verify proper error messages and help documentation\n - Test JSON output format for programmatic usage\n - Validate test task status tracking and reporting functionality",
|
|
||||||
"status": "pending",
|
|
||||||
"dependencies": [
|
|
||||||
1,
|
|
||||||
3,
|
|
||||||
22
|
|
||||||
],
|
|
||||||
"priority": "medium",
|
|
||||||
"subtasks": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 97,
|
|
||||||
"title": "Implement Test Task Execution and Reporting Framework",
|
|
||||||
"description": "Create a comprehensive framework for executing test tasks and generating detailed reports on test results, coverage, and performance metrics.",
|
|
||||||
"details": "Build a robust test execution and reporting system that integrates with the existing test task generation framework:\n\n1. Test Execution Engine:\n - Implement a test runner that can execute different types of test tasks (unit, integration, e2e)\n - Support parallel and sequential test execution modes\n - Handle test timeouts and resource management\n - Provide real-time progress updates during test execution\n - Support test filtering by tags, priority, or test type\n\n2. Test Result Collection:\n - Capture test outcomes (pass/fail/skip) with detailed error messages\n - Record execution times and performance metrics\n - Collect code coverage data when applicable\n - Track test dependencies and execution order\n - Store test artifacts (logs, screenshots, generated files)\n\n3. Reporting System:\n - Generate comprehensive HTML reports with interactive charts\n - Create JSON output for CI/CD integration\n - Implement console reporting with color-coded results\n - Support custom report templates and formats\n - Include trend analysis for test performance over time\n\n4. Integration Features:\n - Connect with existing task management system to update task statuses\n - Support CI/CD pipeline integration with exit codes\n - Implement webhook notifications for test completion\n - Provide API endpoints for external tool integration\n\n5. Configuration Management:\n - Allow customizable test execution settings\n - Support environment-specific test configurations\n - Implement test suite organization and grouping\n - Enable selective test execution based on criteria",
|
|
||||||
"testStrategy": "Verify the test execution framework through comprehensive validation:\n\n1. Unit Testing:\n - Test the test runner engine with mock test cases\n - Validate result collection accuracy with known outcomes\n - Test report generation with various data scenarios\n - Verify configuration parsing and validation\n\n2. Integration Testing:\n - Execute real test suites and verify accurate result capture\n - Test parallel execution with multiple test types\n - Validate report generation with actual test data\n - Test integration with task management system updates\n\n3. Performance Testing:\n - Measure execution overhead and resource usage\n - Test with large test suites to verify scalability\n - Validate timeout handling and resource cleanup\n - Test concurrent execution limits and stability\n\n4. End-to-End Validation:\n - Run complete test cycles from execution to reporting\n - Verify CI/CD integration with sample pipelines\n - Test webhook notifications and external integrations\n - Validate report accuracy against manual test execution\n\n5. Error Handling:\n - Test behavior with failing tests and system errors\n - Verify graceful handling of resource constraints\n - Test recovery from interrupted test executions\n - Validate error reporting and logging accuracy",
|
|
||||||
"status": "pending",
|
|
||||||
"dependencies": [
|
|
||||||
96,
|
|
||||||
22
|
|
||||||
],
|
|
||||||
"priority": "medium",
|
|
||||||
"subtasks": []
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -89,10 +89,10 @@ const DEFAULT_CONFIG = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
account: {
|
account: {
|
||||||
userId: null,
|
userId: "1234567890",
|
||||||
userEmail: "",
|
email: "",
|
||||||
mode: "byok",
|
mode: "byok",
|
||||||
telemetryEnabled: false,
|
telemetryEnabled: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -333,7 +333,6 @@ describe("getConfig Tests", () => {
|
|||||||
},
|
},
|
||||||
global: { ...DEFAULT_CONFIG.global, ...VALID_CUSTOM_CONFIG.global },
|
global: { ...DEFAULT_CONFIG.global, ...VALID_CUSTOM_CONFIG.global },
|
||||||
account: { ...DEFAULT_CONFIG.account },
|
account: { ...DEFAULT_CONFIG.account },
|
||||||
ai: {},
|
|
||||||
};
|
};
|
||||||
expect(config).toEqual(expectedMergedConfig);
|
expect(config).toEqual(expectedMergedConfig);
|
||||||
expect(fsExistsSyncSpy).toHaveBeenCalledWith(MOCK_CONFIG_PATH);
|
expect(fsExistsSyncSpy).toHaveBeenCalledWith(MOCK_CONFIG_PATH);
|
||||||
@@ -357,7 +356,6 @@ describe("getConfig Tests", () => {
|
|||||||
},
|
},
|
||||||
global: { ...DEFAULT_CONFIG.global, ...PARTIAL_CONFIG.global },
|
global: { ...DEFAULT_CONFIG.global, ...PARTIAL_CONFIG.global },
|
||||||
account: { ...DEFAULT_CONFIG.account },
|
account: { ...DEFAULT_CONFIG.account },
|
||||||
ai: {},
|
|
||||||
};
|
};
|
||||||
expect(config).toEqual(expectedMergedConfig);
|
expect(config).toEqual(expectedMergedConfig);
|
||||||
expect(fsReadFileSyncSpy).toHaveBeenCalledWith(MOCK_CONFIG_PATH, "utf-8");
|
expect(fsReadFileSyncSpy).toHaveBeenCalledWith(MOCK_CONFIG_PATH, "utf-8");
|
||||||
@@ -463,7 +461,6 @@ describe("getConfig Tests", () => {
|
|||||||
},
|
},
|
||||||
global: { ...DEFAULT_CONFIG.global, ...INVALID_PROVIDER_CONFIG.global },
|
global: { ...DEFAULT_CONFIG.global, ...INVALID_PROVIDER_CONFIG.global },
|
||||||
account: { ...DEFAULT_CONFIG.account },
|
account: { ...DEFAULT_CONFIG.account },
|
||||||
ai: {},
|
|
||||||
};
|
};
|
||||||
expect(config).toEqual(expectedMergedConfig);
|
expect(config).toEqual(expectedMergedConfig);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -61,12 +61,12 @@ describe("Telemetry Submission Service", () => {
|
|||||||
getConfig.mockReturnValue({
|
getConfig.mockReturnValue({
|
||||||
account: {
|
account: {
|
||||||
userId: "test-user-id",
|
userId: "test-user-id",
|
||||||
|
email: "test@example.com",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mock environment variables for telemetry config
|
// Mock environment variables for telemetry config
|
||||||
process.env.TASKMASTER_SERVICE_ID = "test-api-key";
|
process.env.TASKMASTER_API_KEY = "test-api-key";
|
||||||
process.env.TASKMASTER_USER_EMAIL = "test@example.com";
|
|
||||||
|
|
||||||
// Mock successful response
|
// Mock successful response
|
||||||
global.fetch.mockResolvedValueOnce({
|
global.fetch.mockResolvedValueOnce({
|
||||||
@@ -95,6 +95,7 @@ describe("Telemetry Submission Service", () => {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
"X-Service-ID": "98fb3198-2dfc-42d1-af53-07b99e4f3bde",
|
||||||
Authorization: "Bearer test-api-key",
|
Authorization: "Bearer test-api-key",
|
||||||
"X-User-Email": "test@example.com",
|
"X-User-Email": "test@example.com",
|
||||||
},
|
},
|
||||||
@@ -108,20 +109,19 @@ describe("Telemetry Submission Service", () => {
|
|||||||
expect(sentData.fullOutput).toEqual({ debug: "should-be-sent" });
|
expect(sentData.fullOutput).toEqual({ debug: "should-be-sent" });
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
delete process.env.TASKMASTER_SERVICE_ID;
|
delete process.env.TASKMASTER_API_KEY;
|
||||||
delete process.env.TASKMASTER_USER_EMAIL;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should implement retry logic for failed requests", async () => {
|
it("should implement retry logic for failed requests", async () => {
|
||||||
getConfig.mockReturnValue({
|
getConfig.mockReturnValue({
|
||||||
account: {
|
account: {
|
||||||
userId: "test-user-id",
|
userId: "test-user-id",
|
||||||
|
email: "test@example.com",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mock environment variables
|
// Mock environment variables
|
||||||
process.env.TASKMASTER_SERVICE_ID = "test-api-key";
|
process.env.TASKMASTER_API_KEY = "test-api-key";
|
||||||
process.env.TASKMASTER_USER_EMAIL = "test@example.com";
|
|
||||||
|
|
||||||
// Mock 3 network failures then final HTTP error
|
// Mock 3 network failures then final HTTP error
|
||||||
global.fetch
|
global.fetch
|
||||||
@@ -144,20 +144,19 @@ describe("Telemetry Submission Service", () => {
|
|||||||
expect(global.fetch).toHaveBeenCalledTimes(3);
|
expect(global.fetch).toHaveBeenCalledTimes(3);
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
delete process.env.TASKMASTER_SERVICE_ID;
|
delete process.env.TASKMASTER_API_KEY;
|
||||||
delete process.env.TASKMASTER_USER_EMAIL;
|
|
||||||
}, 10000);
|
}, 10000);
|
||||||
|
|
||||||
it("should handle failures gracefully without blocking execution", async () => {
|
it("should handle failures gracefully without blocking execution", async () => {
|
||||||
getConfig.mockReturnValue({
|
getConfig.mockReturnValue({
|
||||||
account: {
|
account: {
|
||||||
userId: "test-user-id",
|
userId: "test-user-id",
|
||||||
|
email: "test@example.com",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mock environment variables
|
// Mock environment variables
|
||||||
process.env.TASKMASTER_SERVICE_ID = "test-api-key";
|
process.env.TASKMASTER_API_KEY = "test-api-key";
|
||||||
process.env.TASKMASTER_USER_EMAIL = "test@example.com";
|
|
||||||
|
|
||||||
global.fetch.mockRejectedValue(new Error("Network failure"));
|
global.fetch.mockRejectedValue(new Error("Network failure"));
|
||||||
|
|
||||||
@@ -176,8 +175,7 @@ describe("Telemetry Submission Service", () => {
|
|||||||
expect(global.fetch).toHaveBeenCalledTimes(3); // All retries attempted
|
expect(global.fetch).toHaveBeenCalledTimes(3); // All retries attempted
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
delete process.env.TASKMASTER_SERVICE_ID;
|
delete process.env.TASKMASTER_API_KEY;
|
||||||
delete process.env.TASKMASTER_USER_EMAIL;
|
|
||||||
}, 10000);
|
}, 10000);
|
||||||
|
|
||||||
it("should respect user opt-out preferences", async () => {
|
it("should respect user opt-out preferences", async () => {
|
||||||
@@ -216,12 +214,12 @@ describe("Telemetry Submission Service", () => {
|
|||||||
getConfig.mockReturnValue({
|
getConfig.mockReturnValue({
|
||||||
account: {
|
account: {
|
||||||
userId: "test-user-id",
|
userId: "test-user-id",
|
||||||
|
email: "test@example.com",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mock environment variables so config is valid
|
// Mock environment variables so config is valid
|
||||||
process.env.TASKMASTER_SERVICE_ID = "test-api-key";
|
process.env.TASKMASTER_API_KEY = "test-api-key";
|
||||||
process.env.TASKMASTER_USER_EMAIL = "test@example.com";
|
|
||||||
|
|
||||||
const invalidTelemetryData = {
|
const invalidTelemetryData = {
|
||||||
// Missing required fields
|
// Missing required fields
|
||||||
@@ -235,20 +233,19 @@ describe("Telemetry Submission Service", () => {
|
|||||||
expect(global.fetch).not.toHaveBeenCalled();
|
expect(global.fetch).not.toHaveBeenCalled();
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
delete process.env.TASKMASTER_SERVICE_ID;
|
delete process.env.TASKMASTER_API_KEY;
|
||||||
delete process.env.TASKMASTER_USER_EMAIL;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should handle HTTP error responses appropriately", async () => {
|
it("should handle HTTP error responses appropriately", async () => {
|
||||||
getConfig.mockReturnValue({
|
getConfig.mockReturnValue({
|
||||||
account: {
|
account: {
|
||||||
userId: "test-user-id",
|
userId: "test-user-id",
|
||||||
|
email: "test@example.com",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mock environment variables with invalid API key
|
// Mock environment variables with invalid API key
|
||||||
process.env.TASKMASTER_SERVICE_ID = "invalid-key";
|
process.env.TASKMASTER_API_KEY = "invalid-key";
|
||||||
process.env.TASKMASTER_USER_EMAIL = "test@example.com";
|
|
||||||
|
|
||||||
global.fetch.mockResolvedValueOnce({
|
global.fetch.mockResolvedValueOnce({
|
||||||
ok: false,
|
ok: false,
|
||||||
@@ -272,8 +269,7 @@ describe("Telemetry Submission Service", () => {
|
|||||||
expect(global.fetch).toHaveBeenCalledTimes(1); // No retries for auth errors
|
expect(global.fetch).toHaveBeenCalledTimes(1); // No retries for auth errors
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
delete process.env.TASKMASTER_SERVICE_ID;
|
delete process.env.TASKMASTER_API_KEY;
|
||||||
delete process.env.TASKMASTER_USER_EMAIL;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -389,15 +385,16 @@ describe("Telemetry Submission Service", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
global.fetch.mockResolvedValueOnce({
|
global.fetch.mockResolvedValueOnce({
|
||||||
ok: true,
|
ok: false,
|
||||||
json: async () => mockResponse,
|
status: 401,
|
||||||
|
statusText: "Unauthorized",
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await registerUserWithGateway("invalid-email");
|
const result = await registerUserWithGateway("invalid-email");
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
success: false,
|
success: false,
|
||||||
error: "Invalid email format",
|
error: "Gateway registration failed: 401 Unauthorized",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user