graphql-http
Version:
Simple, pluggable, zero-dependency, GraphQL over HTTP spec compliant server, client and audit suite.
145 lines (144 loc) • 4.62 kB
JavaScript
/**
*
* audit/utils
*
*/
export * from '../utils.mjs';
/**
* Wrap and prepare an audit for testing.
*
* @private
*/
export function audit(id, name, fn) {
return {
id,
name,
fn: async () => {
try {
await fn();
return {
id,
name,
status: 'ok',
};
}
catch (err) {
if (!(err instanceof AuditError)) {
// anything thrown that is not an assertion error is considered fatal
throw err;
}
return {
id,
name,
status: name.startsWith('MUST')
? // failing MUSTs are considered errors
'error'
: name.startsWith('SHOULD')
? // recommendations are warnings
'warn'
: // everything else is truly optional
'notice',
reason: err.reason,
response: err.response,
};
}
},
};
}
/**
* Error thrown when an assertion test fails.
*
* @private
*/
export class AuditError {
constructor(response, reason) {
this.response = response;
this.reason = reason;
}
}
/**
* Will throw an AuditError if the assertion on Response fails.
*
* All fatal problems will throw an instance of an Error.
*
* The name "ressert" is a wordplay combining "response" and "assert".
*
* @private
*/
export function ressert(res) {
return {
status: {
toBe(code) {
if (res.status !== code) {
throw new AuditError(res, `Response status code is not ${code}`);
}
},
toBeBetween: (min, max) => {
if (!(min <= res.status && res.status <= max)) {
throw new AuditError(res, `Response status is not between ${min} and ${max}`);
}
},
},
header(key) {
return {
toContain(part) {
var _a;
if (!((_a = res.headers.get(key)) === null || _a === void 0 ? void 0 : _a.includes(part))) {
throw new AuditError(res, `Response header ${key} does not contain ${part}`);
}
},
notToContain(part) {
var _a;
if ((_a = res.headers.get(key)) === null || _a === void 0 ? void 0 : _a.includes(part)) {
throw new AuditError(res, `Response header ${key} contains ${part}`);
}
},
};
},
bodyAsExecutionResult: {
data: {
async toBe(val) {
const clonedRes = res.clone(); // allow the body to be re-read
const body = await assertBodyAsExecutionResult(res);
if (body.data !== val) {
throw new AuditError(clonedRes, `Response body execution result data is not "${val}"`);
}
},
},
async toHaveProperty(key) {
const clonedRes = res.clone(); // allow the body to be re-read
const body = await assertBodyAsExecutionResult(res);
if (!(key in body)) {
throw new AuditError(clonedRes, `Response body execution result does not have a property "${key}"`);
}
},
async notToHaveProperty(key) {
const clonedRes = res.clone(); // allow the body to be re-read
const body = await assertBodyAsExecutionResult(res);
if (key in body) {
throw new AuditError(clonedRes, `Response body execution result has a property "${key}"`);
}
},
},
};
}
/** @private */
async function assertBodyAsExecutionResult(res) {
let decoded;
try {
const decoder = new TextDecoder('utf-8');
const buff = await res.arrayBuffer();
decoded = decoder.decode(buff);
}
catch (err) {
throw new AuditError(res, 'Response body is not UTF-8 encoded');
}
let body;
try {
body = JSON.parse(decoded);
}
catch (err) {
throw new AuditError(res, 'Response body is not valid JSON');
}
return body;
}