fluentrest-ts
Version:
A lightweight, fluent TypeScript API testing library inspired by Java's RestAssured. Built on top of Axios, JSONPath, and Joi for powerful request handling and response validation.
156 lines (155 loc) • 5.99 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ResponseValidatorImpl = void 0;
const assertions_1 = require("../assertions/assertions");
const utils_1 = require("./utils");
const logger_1 = require("./logger");
/**
* A wrapper around the HTTP response (or error),
* allowing safe validation and debugging.
*/
class ResponseValidatorImpl {
constructor(response, error, config, logLevel = "info", logToFile = false, proxyOverride, proxyAgent) {
this.response = response;
this.error = error;
this.config = config;
this.logLevel = logLevel;
this.logToFile = logToFile;
this.proxyOverride = proxyOverride;
this.proxyAgent = proxyAgent;
}
/** Returns true if the request failed due to error or missing response. */
wasFailure() {
return !!this.error || !this.response;
}
/** Returns the raw Axios response object (throws if not available). */
getResponse() {
if (!this.response)
throw new Error("No response available.");
return this.response;
}
/** Returns the Axios request config used to send the request. */
getRequestConfig() {
return {
...this.config,
...(this.proxyOverride ? { proxy: this.proxyOverride } : {}),
...(this.proxyAgent ? { httpAgent: this.proxyAgent, httpsAgent: this.proxyAgent } : {})
};
}
/** Asserts that the response status matches the expected value. */
thenExpectStatus(status) {
if (!this.response)
throw new Error("No response to validate status.");
(0, assertions_1.expectStatus)(this.response, status, this.logLevel, this.logToFile);
return this;
}
/** Asserts that a JSONPath value in the body matches the expected value. */
thenExpectBody(path, expected) {
if (!this.response)
throw new Error("No response to validate body.");
(0, assertions_1.expectBody)(this.response, path, expected, this.logLevel, this.logToFile);
return this;
}
/** Asserts that the body contains a specified fragment of key-values. */
thenExpectBodyContains(fragment) {
if (!this.response)
throw new Error("No response to validate body.");
(0, assertions_1.expectBodyContains)(this.response, fragment, this.logLevel, this.logToFile);
return this;
}
/** Validates the entire body against a Joi schema. */
thenValidateBody(schema) {
if (!this.response)
throw new Error("No response to validate schema.");
(0, assertions_1.validateBody)(this.response, schema, this.logLevel, this.logToFile);
return this;
}
/** Asserts that a response header matches the expected value. */
thenExpectHeader(key, value) {
if (!this.response)
throw new Error("No response to validate header.");
(0, assertions_1.expectHeader)(this.response, key, value, this.logLevel, this.logToFile);
return this;
}
// /** Extracts a value from the response body using a JSONPath. */
thenExtract(path) {
if (!this.response)
throw new Error("No response to extract from.");
return (0, utils_1.extract)(this.response, path);
}
/** Returns the parsed JSON body of the response (throws if unavailable). */
thenJson() {
if (!this.response)
throw new Error("No response available to parse JSON.");
return this.response.data;
}
/**
* Executes a custom assertion or extraction callback
* and logs failure context if it throws.
*/
catchAndLog(fn) {
if (this.error) {
const err = this.error instanceof Error ? this.error : new Error(String(this.error));
// Inject response body for context
if (this.response?.data && typeof this.response.data === 'object') {
err.message += `\nServer response: ${JSON.stringify(this.response.data)}`;
}
if (fn)
fn(err);
else
throw err;
return this;
}
try {
fn?.(new Error("Unexpected call to catchAndLog without error"));
}
catch (err) {
(0, logger_1.logError)(err, "Assertion Failed", this.logLevel, this.logToFile, this.response?.data);
throw err;
}
return this;
}
/** Returns the raw error body (typically from server) if available. */
getErrorBody() {
return this.response?.data;
}
/**
* Runs multiple assertions on the current response context and aggregates any errors.
* This allows for soft-failing multiple expectations without throwing after the first failure.
*
* Each assertion receives the current response object (`this`) and is expected
* to throw an `Error` if it fails. All such errors are caught, aggregated,
* and re-thrown at the end as a combined error with summary output.
*
* @param assertions - Array of functions to run, each receiving the response context.
* @returns `this` for chaining.
*
* @example
* await res.runAssertions([
* r => r.thenExpectStatus(404),
* r => r.thenExpectBody('$.error', 'Not Found'),
* ]).catchAndLog(...);
*/
runAssertions(assertions) {
const errors = [];
for (const fn of assertions) {
try {
fn(this);
}
catch (e) {
if (e instanceof Error) {
errors.push(e);
}
else {
errors.push(new Error(String(e)));
}
}
}
if (errors.length > 0) {
const summary = errors.map(e => e.message).join('\n');
throw new Error(`Multiple assertion failures:\n${summary}`);
}
return this;
}
}
exports.ResponseValidatorImpl = ResponseValidatorImpl;