apiveritas
Version:
Lightweight CLI tool for consumer-driven API contract testing via JSON schema and payload comparisons.
108 lines (107 loc) • 5.16 kB
JavaScript
;
/**
* @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.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ApiCaller = void 0;
const axios_1 = __importDefault(require("axios"));
const ResponseSaver_1 = require("../utils/ResponseSaver");
const Logger_1 = require("../utils/Logger");
const chalk_1 = __importDefault(require("chalk"));
/**
* Executes a list of API requests, logs outcomes, and saves payloads for contract testing.
*/
class ApiCaller {
/**
* 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, logger = new Logger_1.Logger(), baseUrl = '') {
this.baseUrl = baseUrl;
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.
*/
async callAll() {
this.logger.info(`Base URL: ${this.baseUrl}`);
const saver = new ResponseSaver_1.ResponseSaver();
this.logger.info(chalk_1.default.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 = {
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 (0, axios_1.default)(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) {
if (error instanceof Error) {
this.logger.error(chalk_1.default.red(`❌ Failed to call [${req.name}]: ${error.message}`));
// Cast error to any to access response (Axios error)
const anyErr = error;
if (anyErr.response) {
this.logger.error(chalk_1.default.red(`-> HTTP Status: ${anyErr.response.status}`));
this.logger.error(chalk_1.default.red(`-> Response body: ${JSON.stringify(anyErr.response.data)}`));
}
}
else {
this.logger.error(chalk_1.default.red(`❌ Failed to call [${req.name}]: Unknown error`));
}
}
console.log('');
}
}
}
exports.ApiCaller = ApiCaller;