UNPKG

@labnex/cli

Version:

CLI for Labnex, an AI-Powered Testing Automation Platform

415 lines • 17.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.apiClient = exports.LabnexApiClient = void 0; const axios_1 = __importDefault(require("axios")); const chalk_1 = __importDefault(require("chalk")); const config_1 = require("../utils/config"); const cli_table3_1 = __importDefault(require("cli-table3")); class LabnexApiClient { constructor() { this.verboseLogging = false; this.api = axios_1.default.create({ timeout: 120000, headers: { 'Content-Type': 'application/json', }, }); // Request interceptor to add auth token this.api.interceptors.request.use(async (config) => { const userConfig = await (0, config_1.loadConfig)(); // Verbose if set in config OR via global env flag (e.g. --verbose passed to CLI) this.verboseLogging = process.env.LABNEX_VERBOSE === 'true' || userConfig.verbose || false; config.baseURL = userConfig.apiUrl; if (userConfig.token) { config.headers.Authorization = `Bearer ${userConfig.token}`; } if (this.verboseLogging) { console.log(chalk_1.default.gray(`→ ${config.method?.toUpperCase()} ${config.url}`)); } return config; }); // Response interceptor for error handling this.api.interceptors.response.use((response) => { return response; }, (error) => { if (error.response && (error.response.status === 401 || error.response.status === 403)) { const isAiEndpoint = error.config?.url?.includes('/ai') || error.config?.url?.includes('/openai') || error.config?.url?.includes('/ai-'); if (isAiEndpoint) { console.error(chalk_1.default.red('AI optimization failed — check your API key or permissions.')); return Promise.reject(error); } } if (error.response?.status === 401) { console.error(chalk_1.default.red('Authentication failed. Please run: labnex auth login')); process.exit(1); } if (error.response?.status >= 500) { console.error(chalk_1.default.red('Server error. Please try again later.')); } return Promise.reject(error); }); } setVerbose(verbose) { this.verboseLogging = verbose; } /** * Allow commands to turn on verbose logging after the client has been constructed. * (Helpful for sub-commands that parse --verbose themselves.) */ enableVerbose() { this.verboseLogging = true; } // Authentication async login(email, password) { const response = await this.api.post('/auth/login', { email, password }); // Handle both old and new response formats if (response.data.success) { // New format: { success: true, data: { user, token } } return response.data; } else if (response.data.user && response.data.token) { // Old format: { user, token } return { success: true, data: { user: response.data.user, token: response.data.token } }; } else { return { success: false, data: { user: null, token: '' }, error: 'Invalid response format' }; } } async me() { const response = await this.api.get('/auth/me'); return response.data; } // Projects async getProjects() { try { const response = await this.api.get('/projects'); // Backend returns projects directly, not wrapped in success/data format return { success: true, data: response.data }; } catch (error) { return { success: false, data: [], error: error instanceof Error ? error.message : 'Unknown error' }; } } async getProject(projectId) { try { const response = await this.api.get(`/projects/${projectId}`); return { success: true, data: response.data }; } catch (error) { return { success: false, data: { _id: '', name: '', projectCode: '', description: '', isActive: false, testCaseCount: 0, taskCount: 0, owner: { _id: '', name: '', email: '' }, members: [], createdAt: '', updatedAt: '' }, error: error instanceof Error ? error.message : 'Unknown error' }; } } async displayProjectDetails(project) { console.log(chalk_1.default.cyan(`\nšŸ“ ${project.name} (${project.projectCode})`)); console.log(chalk_1.default.gray('─'.repeat(50))); console.log(`${chalk_1.default.bold('Description:')} ${project.description || 'No description'}`); console.log(`${chalk_1.default.bold('Status:')} ${project.isActive ? chalk_1.default.green('Active') : chalk_1.default.gray('Inactive')}`); console.log(`${chalk_1.default.bold('Owner:')} ${project.owner.name} (${project.owner.email})`); console.log(`${chalk_1.default.bold('Created:')} ${new Date(project.createdAt).toLocaleDateString()}`); console.log(`${chalk_1.default.bold('Updated:')} ${new Date(project.updatedAt).toLocaleDateString()}`); console.log(`\n${chalk_1.default.bold('Statistics:')}`); console.log(` Test Cases: ${chalk_1.default.cyan(project.testCaseCount)}`); console.log(` Tasks: ${chalk_1.default.cyan(project.taskCount)}`); console.log(` Team Members: ${chalk_1.default.cyan(project.members.length)}`); if (project.members.length > 0) { console.log(`\n${chalk_1.default.bold('Team Members:')}`); const membersTable = new cli_table3_1.default({ head: ['Name', 'Email', 'Role'], colWidths: [20, 30, 15] }); project.members.forEach((member) => { membersTable.push([ member.name, member.email, member.role ]); }); console.log(membersTable.toString()); } } async createProject(project) { const response = await this.api.post('/projects', project); return { success: true, data: response.data }; } // Test Cases async getTestCases(projectId) { try { const response = await this.api.get(`/projects/${projectId}/test-cases`); // Assuming response.data is the array of TestCase objects, wrap it return { success: true, data: response.data }; } catch (error) { return { success: false, data: [], error: error instanceof Error ? error.message : 'Unknown error' }; } } async createTestCase(projectId, testCase) { try { if (this.verboseLogging) { console.log(chalk_1.default.gray(`[DEBUG] Creating test case for project ${projectId}`)); console.log(chalk_1.default.gray(`[DEBUG] Test case data: ${JSON.stringify(testCase, null, 2)}`)); } const response = await this.api.post(`/projects/${projectId}/test-cases`, testCase); if (this.verboseLogging) { console.log(chalk_1.default.gray(`[DEBUG] Response status: ${response.status}`)); console.log(chalk_1.default.gray(`[DEBUG] Response data: ${JSON.stringify(response.data, null, 2)}`)); } // Temporary workaround: Assume success if HTTP status is 200 or 201, regardless of response content if (response.status === 200 || response.status === 201) { console.log(chalk_1.default.yellow('[DEBUG] Assuming test case save success based on HTTP status ' + response.status + '. Test case likely saved on backend.')); return { success: true, data: response.data || { _id: 'unknown', title: testCase.title, description: testCase.description || '', steps: testCase.steps, expectedResult: testCase.expectedResult, priority: testCase.priority, status: 'PENDING', projectId: projectId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() } }; } // Check if response data indicates success or contains an error if (response.data && response.data.success === false) { return { success: false, data: null, error: response.data.error || response.data.message || 'API reported failure' }; } if (!response.data || (response.data._id === undefined && response.data.id === undefined)) { return { success: false, data: null, error: 'Invalid response format or missing test case ID' }; } return { success: true, data: response.data }; } catch (error) { if (this.verboseLogging) { if (error instanceof Error) { let errorMessage = error.message; if (error.response?.data) { errorMessage += `\n${JSON.stringify(error.response.data)}`; } console.log(chalk_1.default.red(`[DEBUG] Error creating test case: ${errorMessage}`)); } else { console.log(chalk_1.default.red(`[DEBUG] Unknown error creating test case: ${error}`)); } } return { success: false, data: null, error: error instanceof Error ? error.message : 'Unknown error during test case creation' }; } } // Update existing test case (PUT) async updateTestCase(projectId, testCaseId, updates) { try { const response = await this.api.put(`/projects/${projectId}/test-cases/${testCaseId}`, updates); return { success: true, data: response.data }; } catch (error) { return { success: false, data: null, error: error instanceof Error ? error.message : 'Unknown error' }; } } // Test Runs async createTestRun(projectId, config) { const response = await this.api.post(`/projects/${projectId}/test-runs`, config); return response.data; } async getTestRun(runId) { const response = await this.api.get(`/test-runs/${runId}`); return response.data; } async getTestRunResults(runId) { const response = await this.api.get(`/test-runs/${runId}/results`); return response.data; } // Test Runs list for a project async getTestRuns(projectId) { const candidatePaths = [ `/projects/${projectId}/runs`, `/projects/${projectId}/test-runs`, // alt pattern { path: '/runs', params: { projectId } }, // query param style { path: '/test-runs', params: { projectId } } ]; for (const c of candidatePaths) { try { const res = typeof c === 'string' ? await this.api.get(c) : await this.api.get(c.path, { params: c.params }); const possibleArray = Array.isArray(res.data) ? res.data : (res.data && res.data.success && Array.isArray(res.data.data) ? res.data.data : null); if (possibleArray !== null) { return { success: true, data: possibleArray }; } } catch (e) { if (e.response?.status !== 404) { // Non-404 error, stop trying further return { success: false, data: [], error: e instanceof Error ? e.message : 'Unknown error' }; } // else continue to next pattern } } return { success: false, data: [], error: 'Runs endpoint not found (tried multiple variants)' }; } // AI Features async generateTestCase(description) { const response = await this.api.post('/ai/generate-test-case', { description }); return response.data; } async optimizeTestSuite(projectId, codeChanges) { if (this.verboseLogging) { console.log(chalk_1.default.gray(`[DEBUG] Optimizing test suite for project ${projectId}`)); console.log(chalk_1.default.gray(`[DEBUG] Payload: ${JSON.stringify({ codeChanges }, null, 2)}`)); } const response = await this.api.post(`/ai/optimize-test-suite/${projectId}`, { codeChanges }); if (this.verboseLogging) { console.log(chalk_1.default.gray(`[DEBUG] Response status: ${response.status}`)); console.log(chalk_1.default.gray(`[DEBUG] Response data: ${JSON.stringify(response.data, null, 2)}`)); } return response.data; } // Failure Analysis async analyzeFailure(testRunId, failureId) { const response = await this.api.post(`/ai/analyze-failure`, { testRunId, failureId }); return response.data; } async analyzeFailureConversational(testRunId, failureId, conversationHistory, question) { const response = await this.api.post('/ai/analyze-failure-conversational', { testRunId, failureId, conversationHistory, question }); return response.data; } // AI-assisted Test Step Execution async interpretTestStep(stepDescription) { try { if (this.verboseLogging) { console.log(chalk_1.default.gray(`[DEBUG] Interpreting step: "${stepDescription}"`)); } const response = await this.api.post('/ai/interpret-step', { description: stepDescription, }); if (this.verboseLogging) { console.log(chalk_1.default.gray(`[DEBUG] Interpretation response: ${JSON.stringify(response.data)}`)); } if (response.data.success) { return { success: true, data: response.data.data.interpretedStep // Assuming the API returns { success: true, data: { interpretedStep: '...' } } }; } else { return { success: false, data: '', error: response.data.error || 'Failed to interpret step' }; } } catch (error) { return { success: false, data: '', error: error.response?.data?.message || error.message || 'Unknown error' }; } } async suggestAlternative(step, pageContext = '') { try { if (this.verboseLogging) { console.log(chalk_1.default.gray(`[DEBUG] Suggesting alternative for step: "${step}"`)); console.log(chalk_1.default.gray(`[DEBUG] Page context length: ${pageContext.length}`)); } const response = await this.api.post('/ai/suggest-alternative', { step, pageContext, }); if (this.verboseLogging) { console.log(chalk_1.default.gray(`[DEBUG] Suggestion response: ${JSON.stringify(response.data)}`)); } return response.data; // Assuming API returns ApiResponse<string> directly } catch (error) { if (this.verboseLogging) { console.log(chalk_1.default.red(`[DEBUG] Error suggesting alternative: ${error.message}`)); } return { success: false, data: '', error: error.response?.data?.message || error.message || 'Unknown error', }; } } async getDynamicSelectorSuggestion(context) { try { const response = await this.api.post('/ai/suggest-selector', context); return response.data; } catch (error) { return { success: false, data: { suggestedSelector: '' }, error: error.response?.data?.message || error.message || 'Unknown error' }; } } } exports.LabnexApiClient = LabnexApiClient; exports.apiClient = new LabnexApiClient(); //# sourceMappingURL=client.js.map