UNPKG

@apistudio/apim-cli

Version:

CLI for API Management Products

190 lines (178 loc) 5.45 kB
/** * Copyright IBM Corp. 2024, 2025 */ import { LogWrapper } from '../../service/log-wrapper.js'; import { TestExecutionResult, RunFilteredSummary as TestRunFilteredSummary, ExecutionFilteredResult, AssertionSummary, } from '../../models/interface.js'; import { filterSensitiveData, generateCSV, generatePDF, } from '../../helpers/helper.js'; export class TestExecutionReport { private getResponse = (execution: TestExecutionResult) => { if (execution.response.stream) { return execution.response.stream.toString(); } else if (execution.response.data) { return execution.response.data; } else { return 'Response unavailable'; } }; private formatExecution = ( execution: TestExecutionResult, ): ExecutionFilteredResult | null => { try { return { id: execution.id, name: execution.itemName, url: execution.request?.endpoint, method: execution.request?.method, header: execution.request?.headers, time: execution.completedAt - execution.startedAt || 0, responseCode: { code: execution.response?.status || 408, name: execution.response?.statusText || 'Request Timed out', time: execution.response?.responseTime || 0, size: execution.response?.responseSize || 0, }, response: this.getResponse(execution), responseHeaders: execution.response.headers || null, allTests: execution.assertions?.map( ({ error, actualValue, expectedValue, assertion, action, key, metadata, skipped, }) => ({ [assertion]: error ? { status: false, skipped, error, actualValue, expectedValue, action, key, metadata, } : { status: true, skipped, actualValue, expectedValue, action, key, metadata, }, }), ), }; } catch (error) { LogWrapper.logError( '0013', `formatting execution with id ${execution.id}`, (error as Error).message, ); return null; } }; private getExecutionResults( executions: TestExecutionResult[], ): ExecutionFilteredResult[] { try { LogWrapper.logDebug('0003', 'Formatting execution results.'); return executions .map(this.formatExecution) .filter((result) => result !== null); } catch (e) { LogWrapper.logError( '0013', 'processing executions', (e as Error).message, ); return []; } } private createFilteredSummary( collectionId: string, collectionName: string, assertionSummary: AssertionSummary[], results: ExecutionFilteredResult[], startedAt: number, completedAt: number, metadata?: { name: string; version: string; namespace: string }, ): TestRunFilteredSummary { LogWrapper.logDebug( '0003', 'Creating filtered summary from execution results.', ); const totalAssertions = assertionSummary.reduce( (count, item) => count + (item.assertions?.length || 0), 0, ); const totalFailedAssertions = assertionSummary.reduce((count, item) => { return ( count + item.assertions.reduce((innerCount, assertion) => { return innerCount + (assertion.error ? 1 : 0); }, 0) ); }, 0); const summary: TestRunFilteredSummary = { id: collectionId, name: `${collectionName} Collection`, timestamp: completedAt, envMetadata: metadata ?? undefined, totalPass: totalAssertions - totalFailedAssertions, status: 'finished', // TODO Need to confirm the criteria for marking test as failed startedAt: startedAt, totalFail: totalFailedAssertions, totalTime: completedAt - startedAt, results: results, }; return summary; } public collectReport( collectionId: string, collectionName: string, assertionSummary: AssertionSummary[], executions: TestExecutionResult[], startedAt: number, completedAt: number, metadata?: { name: string; version: string; namespace: string }, ): TestRunFilteredSummary { LogWrapper.logInfo('0215', `${collectionName}`); const results = this.getExecutionResults(executions); const filteredSummary = this.createFilteredSummary( collectionId, collectionName, assertionSummary, results, startedAt, completedAt, metadata, ); return filteredSummary; } public getReport(summary: TestRunFilteredSummary[], format: 'PDF' | 'CSV') { // Any environment variables, tokens, or request/response fields marked // as secret, sensitive, or under a specified key pattern (e.g., {}SECRET{}, password, token, etc.) // shall not be included in any exported report. const cleanSummary = summary.map((item) => filterSensitiveData(item)); // The report shall be exportable in PDF and CSV formats. if (format === 'CSV') { return generateCSV(cleanSummary); } else { return generatePDF(cleanSummary); } } }