@ply-ct/ply
Version:
REST API Automated Testing
125 lines (115 loc) • 4.29 kB
text/typescript
import { safeEval } from '@ply-ct/ply-values';
import stringify from 'json-stable-stringify';
import { Options } from './options';
import { fixEol, isJson, isBinary, uintArrayToBase64 } from './util';
export interface Status {
code: number;
message: string;
}
export interface Response {
status: Status;
headers: { [key: string]: string };
/**
* Body can be text, object, ArrayBuffer, or undefined.
*/
body?: any;
submitted?: Date;
time?: number;
}
export class PlyResponse implements Response {
constructor(
readonly runId: string,
readonly status: Status,
readonly headers: { [key: string]: string },
readonly body?: any,
readonly time?: number
) {}
/**
* Orders body object keys unless suppressed by options.
* Sorts arrays if...
* Does not substitute values.
* Response header keys are always lowercase.
* Binary response body is base64 encoded.
* @param runId
* @param options
* @param wantedHeaders optional name of headers subset to keep
* @param stringBody body object is stringified (windows eols are replaced if prettyIndent)
*/
getResponse(
runId: string,
options: Options,
massagers?: ResponseMassagers,
stringBody = false
): PlyResponse {
const headers: { [key: string]: string } = {};
const headerNames = massagers?.headers || Object.keys(this.headers);
headerNames.forEach((h) => {
headers[h.toLowerCase()] = this.headers[h];
});
let body = this.body;
let isJsonString = false;
if (typeof body === 'string' && isJson(body)) {
try {
body = JSON.parse(body);
isJsonString = true;
} catch (err) {
// cannot parse -- body remains string
if (options.verbose) console.debug(err);
}
}
if (isBinary(this.headers, options)) {
if (body) {
body = uintArrayToBase64(new Uint8Array(body));
}
} else if (typeof body === 'object' && massagers?.arraySorts) {
for (const expr of Object.keys(massagers.arraySorts)) {
if (expr === '${response.body}' || expr.startsWith('${response.body.')) {
const arr = safeEval(expr, { response: { body } });
if (Array.isArray(arr)) {
const prop = massagers.arraySorts[expr];
arr.sort((a1, a2) => {
const v1 = a1[prop];
const v2 = a2[prop];
if (v1 === undefined && v2 !== undefined) return -1;
if (v2 === undefined && v1 !== undefined) return 1;
if (typeof v1 === 'number' && typeof v2 === 'number') return v1 - v2;
return ('' + v1).localeCompare('' + v2);
});
}
}
}
}
if (stringBody && typeof body === 'object') {
if (options.responseBodySortedKeys) {
body = stringify(body, { space: ''.padStart(options.prettyIndent || 0, ' ') });
} else {
body = JSON.stringify(body, null, options.prettyIndent);
}
} else if (
!isJsonString &&
typeof body === 'string' &&
body.trim().startsWith('<') &&
options.prettyIndent
) {
// format XML
try {
const xmlOpts = {
indentation: ''.padStart(options.prettyIndent, ' '),
collapseContent: true
};
body = require('xml-formatter')(body, xmlOpts);
} catch (err) {
// cannot format
if (options.verbose) console.debug(err);
}
}
if (body && stringBody && options.prettyIndent) {
body = fixEol(body);
}
return new PlyResponse(runId, this.status, headers, body, this.time);
}
}
export interface ResponseMassagers {
headers?: string[];
arraySorts?: { [expr: string]: string };
}