UNPKG

apiveritas

Version:

Lightweight CLI tool for consumer-driven API contract testing via JSON schema and payload comparisons.

125 lines (102 loc) 4.58 kB
/** * @file ApiCaller.ts * @author Mario Galea * @description * Responsible for executing a list of API requests defined in a Test Suite. * This class handles HTTP request logic using Axios, saves the responses to disk, and logs execution details. */ import axios from 'axios'; import { IApiRequest } from '../../interfaces/IApiRequest'; import { ResponseSaver } from '../utils/ResponseSaver'; import { Logger } from '../utils/Logger'; import chalk from 'chalk'; type AxiosRequestConfig = Parameters<typeof axios>[0]; /** * Executes a list of API requests, logs outcomes, and saves payloads for contract testing. */ export class ApiCaller { private requests: IApiRequest[]; private logger: Logger; /** * Constructs a new ApiCaller instance. * * @param requests - An array of API requests to be executed. * @param logger - A logger instance for structured logging. Defaults to a new Logger instance. * @param baseUrl - The base URL to prepend to all request paths unless overridden per request. */ constructor(requests: IApiRequest[], logger: Logger = new Logger(), private baseUrl: string = '') { this.requests = requests; this.logger = logger; this.baseUrl = baseUrl; } /** * Iterates over the provided list of API requests, executes them using Axios, and saves each response to disk. * * - Logs all actions using the provided Logger. * - Automatically validates HTTP status codes if `expectedStatus` is defined in the request. * - Responses are saved to the payloads directory using the ResponseSaver utility. * - Supports per-request `baseUrl` override (useful for mock server mode). * * @returns A Promise that resolves when all API calls have completed (successfully or with handled failure). * * @throws If a request is missing its required `testSuite` field, an error is thrown immediately. * If a request is missing its `url`, an error is logged and the request is skipped. */ public async callAll(): Promise<void> { this.logger.info(`Base URL: ${this.baseUrl}`); const saver = new ResponseSaver(); this.logger.info(chalk.white.bold.underline('Test Run:\n')); for (const req of this.requests) { const method = (req.method ?? 'GET').toUpperCase(); const safeName = req.name.replace(/\s+/g, '_'); const testSuite = req.testSuite; if (!testSuite) { throw new Error(`Missing testSuite name in request: ${req.name}`); } if (!req.url) { this.logger.error(`Missing URL in request: ${req.name}`); continue; } // Allow per-request base URL override const effectiveBaseUrl = req.baseUrl ?? this.baseUrl; const fullUrl = effectiveBaseUrl + req.url; console.log(fullUrl); this.logger.info(`Calling URL: ${fullUrl} (method: ${method})`); try { const axiosConfig: AxiosRequestConfig = { url: fullUrl, method, auth: req.auth, data: req.body ?? undefined, validateStatus: () => true, // allow all HTTP responses }; this.logger.debug(`Calling [${req.name}] with config:`); this.logger.debug(JSON.stringify(axiosConfig, null, 2)); const response = await axios(axiosConfig); if (req.expectedStatus && response.status !== req.expectedStatus) { this.logger.error(`[${req.name}] returned status ${response.status}, expected ${req.expectedStatus}`); } else { this.logger.info(`[${req.name}] returned expected status ${response.status}`); } this.logger.debug(`Response from [${req.name}]`); saver.saveResponse(testSuite, safeName, response.data); this.logger.info( `Saved response: payloads/${saver.getTimestampFolderName()}/${testSuite}/${safeName}.json` ); } catch (error: unknown) { if (error instanceof Error) { this.logger.error(chalk.red(`❌ Failed to call [${req.name}]: ${error.message}`)); // Cast error to any to access response (Axios error) const anyErr = error as any; if (anyErr.response) { this.logger.error(chalk.red(`-> HTTP Status: ${anyErr.response.status}`)); this.logger.error(chalk.red(`-> Response body: ${JSON.stringify(anyErr.response.data)}`)); } } else { this.logger.error(chalk.red(`❌ Failed to call [${req.name}]: Unknown error`)); } } console.log(''); } } }