@node-scarlet/http
Version:
Build APIs Instantly 🏋️♂️
397 lines (336 loc) • 10.8 kB
text/typescript
import { strict as assert } from 'assert';
const { URLSearchParams } = require('url');
import * as http from './http';
const { GET, POST } = http.methods;
import * as fetch from 'node-fetch';
export const tests = [
portZeroTest,
defaultHeadersTest,
handlerMetaTest,
illegalRouteMethodsTest,
requestBodyParserURLencodedTest,
requestBodyParserJsonTest,
requestUrlTest,
requestQueryTest,
requestRedirectTest,
defaultBodyTest,
responseShortHandTest,
partialMatchTest,
asyncHandlerTest,
nullBodyTest,
];
async function portZeroTest() {
const description = `port 0 can be used to
guarantee an available port`;
try {
const requests = http.server();
await requests.listen();
assert(requests.port());
await requests.close();
return true;
} catch (e) {
return e;
}
}
async function defaultHeadersTest() {
const description = `Server responses should specify certain
headers by default`;
try {
// Start up an http server
const requests = http.server();
requests.route(GET, '/', (req, meta) => {
return http.response({
status: 200,
headers: {},
body: ''
})
})
await requests.listen();
// Make A request to the server defined above
const res = await fetch(`http://0.0.0.0:${requests.port()}`);
assert.equal(res.status, 200);
const { length } = Object.keys(res.headers.raw());
assert.equal(length, 3);
assert.ok(res.headers.get('date'));
assert.equal(res.headers.get('connection'), 'close');
await requests.close();
} catch (e) {
return e;
}
}
async function handlerMetaTest() {
const description = `Request handlers should be able
to set metafields that are accessible to later handlers`;
try {
// Start up an http server
const requests = http.server();
requests.route(GET, '/', (req, meta) => {
meta.desire = 'love';
})
requests.route(GET, '/', (req, meta) => {
return http.response({
status: 200,
headers: {},
body: 'Wilber didn\'t want food. He wanted ' + meta.desire,
})
})
await requests.listen();
// Make A request to the server defined above
const res = await fetch(`http://0.0.0.0:${requests.port()}`);
assert.equal(res.status, 200);
assert.equal(await res.text(), 'Wilber didn\'t want food. He wanted love');
await requests.close();
}
catch (e) {
return e;
}
}
async function illegalRouteMethodsTest() {
const description = `Registering Unsupported
request methods should throw an error`;
try {
const registerIllegalRoute = () => {
const requests = http.server();
requests.route('SAVE', '/', (req, meta) => {
return http.response();
})
}
assert.throws(registerIllegalRoute, {
message: 'Unsupported verb "SAVE".'
})
} catch (e) {
return e;
}
}
async function requestBodyParserURLencodedTest() {
const description = `URL encoded body content
will be parsed into an object`;
try {
const requests = http.server();
requests.route(POST, '/', (req, meta) => {
assert.equal(
typeof req.body,
'object'
);
assert.deepEqual(
{ name: 'charlotte' },
req.body,
);
})
await requests.listen();
const params = new URLSearchParams();
params.append('name', 'charlotte');
await fetch(`http://0.0.0.0:${requests.port()}`, {
method: 'POST',
headers: {
'content-type': 'application/x-www-form-urlencoded'
},
body: params,
});
await requests.close();
} catch (e) {
return e;
}
}
async function requestBodyParserJsonTest() {
const description = `JSON encoded body content
will be parsed into an object`;
try {
const requests = http.server();
requests.route(POST, '/', (req, meta) => {
assert.equal(
typeof req.body,
'object'
);
assert.deepEqual(
{ name: 'wilbur' },
req.body,
);
})
await requests.listen();
const res = await fetch(`http://0.0.0.0:${requests.port()}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'wilbur' }),
});
await requests.close();
} catch (e) {
return e;
}
}
async function requestUrlTest() {
const description = `Requests should have
a url property`;
try {
const requests = http.server();
requests.route(GET, '/', (req, meta) => {
assert.equal(
req.url,
'/?idea=special'
);
})
await requests.listen();
await fetch(`http://0.0.0.0:${requests.port()}/?idea=special`);
await requests.close();
} catch (e) {
return e;
}
}
async function requestQueryTest() {
const description = `Requests should have
a query property that represents querystring
parameters`;
try {
const requests = http.server();
requests.route('GET', '/thinking', (req, meta) => {
// query is a null-prototype object, so deepEqual doesn't work as one might expect
assert.equal(
JSON.stringify(req.query),
JSON.stringify({ splish: 'splash' })
);
})
requests.route('GET', '/everything/was', (req, meta) => {
assert.equal(
JSON.stringify(req.query),
JSON.stringify({ i: 'was', taking: 'a bath'})
);
})
requests.route('GET', '/alright', (req, meta) => {
assert.equal(
JSON.stringify(req.query),
JSON.stringify({ long: 'about', 'a saturday': 'night' })
);
})
await requests.listen();
await fetch(`http://0.0.0.0:${requests.port()}/thinking?splish=splash`);
await fetch(`http://0.0.0.0:${requests.port()}/everything/was?i=was&taking=a%20bath`);
await fetch(`http://0.0.0.0:${requests.port()}/alright?long=about&a%20saturday=night`);
await requests.close();
} catch (e) {
return e;
}
}
async function requestRedirectTest() {
const description = `Redirects should be possible
using a response constructor`;
try {
const first = http.server();
const second = http.server();
first.route('GET', '/', (req, meta) => {
const location = `http://0.0.0.0:${second.port()}`;
return http.response({
status: 307,
headers: { location },
body: '',
})
})
second.route('GET', '/', (req, meta) => 'Success!')
await first.listen();
await second.listen();
const response = await fetch(`http://0.0.0.0:${first.port()}`);
assert.equal(
await response.text(),
'Success!'
);
await first.close();
await second.close();
} catch (e) {
return e;
}
}
async function defaultBodyTest() {
const description = `If a response has a status but no
body, the status code message should be the body`;
try {
const requests = http.server();
requests.route('GET', '/200', (req, meta) => 200);
requests.route('GET', '/201', (req, meta) => 201);
requests.route('GET', '/400', (req, meta) => 400);
requests.route('GET', '/401', (req, meta) => 401);
requests.route('GET', '/403', (req, meta) => 403);
requests.route('GET', '/404', (req, meta) => 404);
requests.route('GET', '/409', (req, meta) => 409);
requests.route('GET', '/500', (req, meta) => 500);
await requests.listen();
assert.equal('OK', await (await fetch(`http://0.0.0.0:${requests.port()}/200`)).text());
assert.equal('Created', await (await fetch(`http://0.0.0.0:${requests.port()}/201`)).text());
assert.equal('Bad Request', await (await fetch(`http://0.0.0.0:${requests.port()}/400`)).text());
assert.equal('Unauthorized', await (await fetch(`http://0.0.0.0:${requests.port()}/401`)).text());
assert.equal('Forbidden', await (await fetch(`http://0.0.0.0:${requests.port()}/403`)).text());
assert.equal('Not Found', await (await fetch(`http://0.0.0.0:${requests.port()}/404`)).text());
assert.equal('Conflict', await (await fetch(`http://0.0.0.0:${requests.port()}/409`)).text());
assert.equal('Internal Server Error', await (await fetch(`http://0.0.0.0:${requests.port()}/500`)).text());
await requests.close();
} catch (e) {
return e;
}
}
async function responseShortHandTest() {
const description = `Handlers can return status codes, strings,
and objects as a shortcut`;
try {
const requests = http.server();
requests.route('GET', '/num', (req, meta) => 200);
requests.route('GET', '/bad', (req, meta) => 400);
requests.route('GET', '/str', (req, meta) => 'hello');
requests.route('GET', '/obj', (req, meta) => ({ data: 'success' }));
await requests.listen();
assert.equal('OK', await (await fetch(`http://0.0.0.0:${requests.port()}/num`)).text());
assert.equal('Bad Request', await (await fetch(`http://0.0.0.0:${requests.port()}/bad`)).text());
assert.equal('hello', await (await fetch(`http://0.0.0.0:${requests.port()}/str`)).text());
assert.deepEqual({ data: 'success' }, await (await fetch(`http://0.0.0.0:${requests.port()}/obj`)).json());
await requests.close();
} catch (e) {
return e;
}
}
async function partialMatchTest() {
const description = `Errors thrown by url matching
are caught, and the middleware that caused it is ignored`;
try {
const requests = http.server();
// Notice the orphaned paren in the pattern
requests.route('GET', '/cache/set/:key)', (req, meta) => 200);
await requests.listen();
const triggerMalformedRoute = async () => {
const response = await fetch(`http://0.0.0.0:${requests.port()}/cache/set/hello`);
await requests.close();
}
await assert.doesNotThrow(triggerMalformedRoute);
} catch (e) {
return e;
}
}
async function asyncHandlerTest() {
const description = `Promises returned by Handlers should be
valid material for http responses`;
try {
const requests = http.server();
requests.route('GET', '/*', async (req, meta) => 'Hello!');
await requests.listen();
const response = await fetch(`http://0.0.0.0:${requests.port()}`);
assert.equal(await response.text(), 'Hello!');
await requests.close();
} catch (e) {
return e;
}
}
async function nullBodyTest() {
const description = `Null body values
are valid response material`;
try {
const requests = http.server();
requests.route('GET', '/*', async (req, meta) => {
return http.response({
headers: { 'content-type': 'application/json' },
body: JSON.stringify(null),
});
});
await requests.listen();
const response = await fetch(`http://0.0.0.0:${requests.port()}`);
assert.deepEqual(await response.json(), null);
await requests.close();
} catch (e) {
return e;
}
}