Files
bun.sh/test/js/third_party/express/express.json.test.ts

746 lines
23 KiB
TypeScript

"use strict";
var assert = require("node:assert");
var AsyncLocalStorage = require("node:async_hooks").AsyncLocalStorage;
var express = require("express");
var request = require("supertest");
describe("express.json()", function () {
let app;
it("should parse JSON", function (done) {
request(createApp())
.post("/")
.set("Content-Type", "application/json")
.send('{"user":"tobi"}')
.expect(200, '{"user":"tobi"}', done);
});
it("should handle Content-Length: 0", function (done) {
request(createApp())
.post("/")
.set("Content-Type", "application/json")
.set("Content-Length", "0")
.expect(200, "{}", done);
});
it.todo("should handle empty message-body", function (done) {
request(createApp())
.post("/")
.set("Content-Type", "application/json")
.set("Transfer-Encoding", "chunked")
.expect(200, "{}", done);
});
it("should handle no message-body", function (done) {
request(createApp())
.post("/")
.set("Content-Type", "application/json")
.unset("Transfer-Encoding")
.expect(200, "{}", done);
});
// The old node error message modification in body parser is catching this
it("should 400 when only whitespace", function (done) {
request(createApp())
.post("/")
.set("Content-Type", "application/json")
.send(" \n")
.expect(400, "[entity.parse.failed] " + parseError(" \n"), done);
});
it("should 400 when invalid content-length", function (done) {
var app = express();
app.use(function (req, res, next) {
req.headers["content-length"] = "20"; // bad length
next();
});
app.use(express.json());
app.post("/", function (req, res) {
res.json(req.body);
});
request(app)
.post("/")
.set("Content-Type", "application/json")
.send('{"str":')
.expect(400, /content length/, done);
});
it("should handle duplicated middleware", function (done) {
var app = express();
app.use(express.json());
app.use(express.json());
app.post("/", function (req, res) {
res.json(req.body);
});
request(app)
.post("/")
.set("Content-Type", "application/json")
.send('{"user":"tobi"}')
.expect(200, '{"user":"tobi"}', done);
});
describe("when JSON is invalid", function () {
let app;
beforeAll(function () {
app = createApp();
});
it("should 400 for bad token", function (done) {
request(app)
.post("/")
.set("Content-Type", "application/json")
.send("{:")
.expect(400, "[entity.parse.failed] " + parseError("{:"), done);
});
it("should 400 for incomplete", function (done) {
request(app)
.post("/")
.set("Content-Type", "application/json")
.send('{"user"')
.expect(400, "[entity.parse.failed] " + parseError('{"user"'), done);
});
it("should include original body on error object", function (done) {
request(app)
.post("/")
.set("Content-Type", "application/json")
.set("X-Error-Property", "body")
.send(' {"user"')
.expect(400, ' {"user"', done);
});
});
describe("with limit option", function () {
it("should 413 when over limit with Content-Length", function (done) {
var buf = Buffer.alloc(1024, ".");
request(createApp({ limit: "1kb" }))
.post("/")
.set("Content-Type", "application/json")
.set("Content-Length", "1034")
.send(JSON.stringify({ str: buf.toString() }))
.expect(413, "[entity.too.large] request entity too large", done);
});
it("should 413 when over limit with chunked encoding", function (done) {
var app = createApp({ limit: "1kb" });
var buf = Buffer.alloc(1024, ".");
var test = request(app).post("/");
test.set("Content-Type", "application/json");
test.set("Transfer-Encoding", "chunked");
test.write('{"str":');
test.write('"' + buf.toString() + '"}');
test.expect(413, done);
});
it("should 413 when inflated body over limit", function (done) {
var app = createApp({ limit: "1kb" });
var test = request(app).post("/");
test.set("Content-Encoding", "gzip");
test.set("Content-Type", "application/json");
test.write(Buffer.from("1f8b080000000000000aab562a2e2952b252d21b05a360148c58a0540b0066f7ce1e0a040000", "hex"));
test.expect(413, done);
});
it("should accept number of bytes", function (done) {
var buf = Buffer.alloc(1024, ".");
request(createApp({ limit: 1024 }))
.post("/")
.set("Content-Type", "application/json")
.send(JSON.stringify({ str: buf.toString() }))
.expect(413, done);
});
it("should not change when options altered", function (done) {
var buf = Buffer.alloc(1024, ".");
var options = { limit: "1kb" };
var app = createApp(options);
options.limit = "100kb";
request(app)
.post("/")
.set("Content-Type", "application/json")
.send(JSON.stringify({ str: buf.toString() }))
.expect(413, done);
});
it("should not hang response", function (done) {
var buf = Buffer.alloc(10240, ".");
var app = createApp({ limit: "8kb" });
var test = request(app).post("/");
test.set("Content-Type", "application/json");
test.write(buf);
test.write(buf);
test.write(buf);
test.expect(413, done);
});
it("should not error when inflating", function (done) {
var app = createApp({ limit: "1kb" });
var test = request(app).post("/");
test.set("Content-Encoding", "gzip");
test.set("Content-Type", "application/json");
test.write(Buffer.from("1f8b080000000000000aab562a2e2952b252d21b05a360148c58a0540b0066f7ce1e0a0400", "hex"));
test.expect(413, done);
});
});
describe("with inflate option", function () {
describe("when false", function () {
beforeAll(function () {
app = createApp({ inflate: false });
});
it.todo("should not accept content-encoding", function (done) {
var test = request(app).post("/");
test.set("Content-Encoding", "gzip");
test.set("Content-Type", "application/json");
test.write(Buffer.from("1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000", "hex"));
test.expect(415, "[encoding.unsupported] content encoding unsupported", done);
});
});
describe("when true", function () {
beforeAll(function () {
app = createApp({ inflate: true });
});
it("should accept content-encoding", function (done) {
var test = request(app).post("/");
test.set("Content-Encoding", "gzip");
test.set("Content-Type", "application/json");
test.write(Buffer.from("1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000", "hex"));
test.expect(200, '{"name":"论"}', done);
});
});
});
describe("with strict option", function () {
describe("when undefined", function () {
beforeAll(function () {
app = createApp();
});
it("should 400 on primitives", function (done) {
request(app)
.post("/")
.set("Content-Type", "application/json")
.send("true")
.expect(400, "[entity.parse.failed] " + parseError("#rue").replace(/#/g, "t"), done);
});
});
describe("when false", function () {
beforeAll(function () {
app = createApp({ strict: false });
});
it.todo("should parse primitives", function (done) {
request(app).post("/").set("Content-Type", "application/json").send("true").expect(200, "true", done);
});
});
describe("when true", function () {
beforeAll(function () {
app = createApp({ strict: true });
});
it("should not parse primitives", function (done) {
request(app)
.post("/")
.set("Content-Type", "application/json")
.send("true")
.expect(400, "[entity.parse.failed] " + parseError("#rue").replace(/#/g, "t"), done);
});
it("should not parse primitives with leading whitespaces", function (done) {
request(app)
.post("/")
.set("Content-Type", "application/json")
.send(" true")
.expect(400, "[entity.parse.failed] " + parseError(" #rue").replace(/#/g, "t"), done);
});
it("should allow leading whitespaces in JSON", function (done) {
request(app)
.post("/")
.set("Content-Type", "application/json")
.send(' { "user": "tobi" }')
.expect(200, '{"user":"tobi"}', done);
});
it("should include correct message in stack trace", function (done) {
request(app)
.post("/")
.set("Content-Type", "application/json")
.set("X-Error-Property", "stack")
.send("true")
.expect(400)
.expect(shouldContainInBody(parseError("#rue").replace(/#/g, "t")))
.end(done);
});
});
});
describe("with type option", function () {
describe.skip('when "application/vnd.api+json"', function () {
beforeAll(function () {
app = createApp({ type: "application/vnd.api+json" });
});
it("should parse JSON for custom type", function (done) {
request(app)
.post("/")
.set("Content-Type", "application/vnd.api+json")
.send('{"user":"tobi"}')
.expect(200, '{"user":"tobi"}', done);
});
it("should ignore standard type", function (done) {
request(app).post("/").set("Content-Type", "application/json").send('{"user":"tobi"}').expect(200, "", done);
});
});
describe('when ["application/json", "application/vnd.api+json"]', function () {
beforeAll(function () {
app = createApp({
type: ["application/json", "application/vnd.api+json"],
});
});
it('should parse JSON for "application/json"', function (done) {
request(app)
.post("/")
.set("Content-Type", "application/json")
.send('{"user":"tobi"}')
.expect(200, '{"user":"tobi"}', done);
});
it.todo('should parse JSON for "application/vnd.api+json"', function (done) {
request(app)
.post("/")
.set("Content-Type", "application/vnd.api+json")
.send('{"user":"tobi"}')
.expect(200, '{"user":"tobi"}', done);
});
it.todo('should ignore "application/x-json"', function (done) {
request(app).post("/").set("Content-Type", "application/x-json").send('{"user":"tobi"}').expect(200, "", done);
});
});
describe("when a function", function () {
it("should parse when truthy value returned", function (done) {
var app = createApp({ type: accept });
function accept(req) {
return req.headers["content-type"] === "application/vnd.api+json";
}
request(app)
.post("/")
.set("Content-Type", "application/vnd.api+json")
.send('{"user":"tobi"}')
.expect(200, '{"user":"tobi"}', done);
});
it("should work without content-type", function (done) {
var app = createApp({ type: accept });
function accept(req) {
return true;
}
var test = request(app).post("/");
test.write('{"user":"tobi"}');
test.expect(200, '{"user":"tobi"}', done);
});
it("should not invoke without a body", function (done) {
var app = createApp({ type: accept });
function accept(req) {
throw new Error("oops!");
}
request(app).get("/").expect(404, done);
});
});
});
describe("with verify option", function () {
it("should assert value if function", function () {
assert.throws(createApp.bind(null, { verify: "lol" }), /TypeError: option verify must be function/);
});
it("should error from verify", function (done) {
var app = createApp({
verify: function (req, res, buf) {
if (buf[0] === 0x5b) throw new Error("no arrays");
},
});
request(app)
.post("/")
.set("Content-Type", "application/json")
.send('["tobi"]')
.expect(403, "[entity.verify.failed] no arrays", done);
});
it("should allow custom codes", function (done) {
var app = createApp({
verify: function (req, res, buf) {
if (buf[0] !== 0x5b) return;
var err = new Error("no arrays");
err.status = 400;
throw err;
},
});
request(app)
.post("/")
.set("Content-Type", "application/json")
.send('["tobi"]')
.expect(400, "[entity.verify.failed] no arrays", done);
});
it("should allow custom type", function (done) {
var app = createApp({
verify: function (req, res, buf) {
if (buf[0] !== 0x5b) return;
var err = new Error("no arrays");
err.type = "foo.bar";
throw err;
},
});
request(app)
.post("/")
.set("Content-Type", "application/json")
.send('["tobi"]')
.expect(403, "[foo.bar] no arrays", done);
});
it("should include original body on error object", function (done) {
var app = createApp({
verify: function (req, res, buf) {
if (buf[0] === 0x5b) throw new Error("no arrays");
},
});
request(app)
.post("/")
.set("Content-Type", "application/json")
.set("X-Error-Property", "body")
.send('["tobi"]')
.expect(403, '["tobi"]', done);
});
it("should allow pass-through", function (done) {
var app = createApp({
verify: function (req, res, buf) {
if (buf[0] === 0x5b) throw new Error("no arrays");
},
});
request(app)
.post("/")
.set("Content-Type", "application/json")
.send('{"user":"tobi"}')
.expect(200, '{"user":"tobi"}', done);
});
it("should work with different charsets", function (done) {
var app = createApp({
verify: function (req, res, buf) {
if (buf[0] === 0x5b) throw new Error("no arrays");
},
});
var test = request(app).post("/");
test.set("Content-Type", "application/json; charset=utf-16");
test.write(Buffer.from("feff007b0022006e0061006d00650022003a00228bba0022007d", "hex"));
test.expect(200, '{"name":"论"}', done);
});
it("should 415 on unknown charset prior to verify", function (done) {
var app = createApp({
verify: function (req, res, buf) {
throw new Error("unexpected verify call");
},
});
var test = request(app).post("/");
test.set("Content-Type", "application/json; charset=x-bogus");
test.write(Buffer.from("00000000", "hex"));
test.expect(415, '[charset.unsupported] unsupported charset "X-BOGUS"', done);
});
});
describe.todo("async local storage", function () {
beforeAll(function () {
var app = express();
var store = { foo: "bar" };
app.use(function (req, res, next) {
req.asyncLocalStorage = new AsyncLocalStorage();
req.asyncLocalStorage.run(store, next);
});
app.use(express.json());
app.use(function (req, res, next) {
var local = req.asyncLocalStorage.getStore();
if (local) {
res.setHeader("x-store-foo", String(local.foo));
}
next();
});
app.use(function (err, req, res, next) {
var local = req.asyncLocalStorage.getStore();
if (local) {
res.setHeader("x-store-foo", String(local.foo));
}
res.status(err.status || 500);
res.send("[" + err.type + "] " + err.message);
});
app.post("/", function (req, res) {
res.json(req.body);
});
app = app;
});
it("should presist store", function (done) {
request(app)
.post("/")
.set("Content-Type", "application/json")
.send('{"user":"tobi"}')
.expect(200)
.expect("x-store-foo", "bar")
.expect('{"user":"tobi"}')
.end(done);
});
it("should persist store when unmatched content-type", function (done) {
request(app)
.post("/")
.set("Content-Type", "application/fizzbuzz")
.send("buzz")
.expect(200)
.expect("x-store-foo", "bar")
.expect("")
.end(done);
});
it("should presist store when inflated", function (done) {
var test = request(app).post("/");
test.set("Content-Encoding", "gzip");
test.set("Content-Type", "application/json");
test.write(Buffer.from("1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000", "hex"));
test.expect(200);
test.expect("x-store-foo", "bar");
test.expect('{"name":"论"}');
test.end(done);
});
it("should presist store when inflate error", function (done) {
var test = request(app).post("/");
test.set("Content-Encoding", "gzip");
test.set("Content-Type", "application/json");
test.write(Buffer.from("1f8b080000000000000bab56cc4d55b2527ab16e97522d00515be1cc0e000000", "hex"));
test.expect(400);
test.expect("x-store-foo", "bar");
test.end(done);
});
it("should presist store when parse error", function (done) {
request(app)
.post("/")
.set("Content-Type", "application/json")
.send('{"user":')
.expect(400)
.expect("x-store-foo", "bar")
.end(done);
});
it("should presist store when limit exceeded", function (done) {
request(app)
.post("/")
.set("Content-Type", "application/json")
.send('{"user":"' + Buffer.alloc(1024 * 100, ".").toString() + '"}')
.expect(413)
.expect("x-store-foo", "bar")
.end(done);
});
});
describe("charset", function () {
beforeAll(function () {
app = createApp();
});
it("should parse utf-8", function (done) {
var test = request(app).post("/");
test.set("Content-Type", "application/json; charset=utf-8");
test.write(Buffer.from("7b226e616d65223a22e8aeba227d", "hex"));
test.expect(200, '{"name":"论"}', done);
});
it("should parse utf-16", function (done) {
var test = request(app).post("/");
test.set("Content-Type", "application/json; charset=utf-16");
test.write(Buffer.from("feff007b0022006e0061006d00650022003a00228bba0022007d", "hex"));
test.expect(200, '{"name":"论"}', done);
});
it("should parse when content-length != char length", function (done) {
var test = request(app).post("/");
test.set("Content-Type", "application/json; charset=utf-8");
test.set("Content-Length", "13");
test.write(Buffer.from("7b2274657374223a22c3a5227d", "hex"));
test.expect(200, '{"test":"å"}', done);
});
it("should default to utf-8", function (done) {
var test = request(app).post("/");
test.set("Content-Type", "application/json");
test.write(Buffer.from("7b226e616d65223a22e8aeba227d", "hex"));
test.expect(200, '{"name":"论"}', done);
});
it("should fail on unknown charset", function (done) {
var test = request(app).post("/");
test.set("Content-Type", "application/json; charset=koi8-r");
test.write(Buffer.from("7b226e616d65223a22cec5d4227d", "hex"));
test.expect(415, '[charset.unsupported] unsupported charset "KOI8-R"', done);
});
});
describe("encoding", function () {
let app;
beforeAll(function () {
app = createApp({ limit: "1kb" });
});
it("should parse without encoding", function (done) {
var test = request(app).post("/");
test.set("Content-Type", "application/json");
test.write(Buffer.from("7b226e616d65223a22e8aeba227d", "hex"));
test.expect(200, '{"name":"论"}', done);
});
it("should support identity encoding", function (done) {
var test = request(app).post("/");
test.set("Content-Encoding", "identity");
test.set("Content-Type", "application/json");
test.write(Buffer.from("7b226e616d65223a22e8aeba227d", "hex"));
test.expect(200, '{"name":"论"}', done);
});
it("should support gzip encoding", function (done) {
var test = request(app).post("/");
test.set("Content-Encoding", "gzip");
test.set("Content-Type", "application/json");
test.write(Buffer.from("1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000", "hex"));
test.expect(200, '{"name":"论"}', done);
});
it("should support deflate encoding", function (done) {
var test = request(app).post("/");
test.set("Content-Encoding", "deflate");
test.set("Content-Type", "application/json");
test.write(Buffer.from("789cab56ca4bcc4d55b2527ab16e97522d00274505ac", "hex"));
test.expect(200, '{"name":"论"}', done);
});
it("should be case-insensitive", function (done) {
var test = request(app).post("/");
test.set("Content-Encoding", "GZIP");
test.set("Content-Type", "application/json");
test.write(Buffer.from("1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000", "hex"));
test.expect(200, '{"name":"论"}', done);
});
it("should 415 on unknown encoding", function (done) {
var test = request(app).post("/");
test.set("Content-Encoding", "nulls");
test.set("Content-Type", "application/json");
test.write(Buffer.from("000000000000", "hex"));
test.expect(415, '[encoding.unsupported] unsupported content encoding "nulls"', done);
});
it("should 400 on malformed encoding", function (done) {
var test = request(app).post("/");
test.set("Content-Encoding", "gzip");
test.set("Content-Type", "application/json");
test.write(Buffer.from("1f8b080000000000000bab56cc4d55b2527ab16e97522d00515be1cc0e000000", "hex"));
test.expect(400, done);
});
it("should 413 when inflated value exceeds limit", function (done) {
// gzip'd data exceeds 1kb, but deflated below 1kb
var test = request(app).post("/");
test.set("Content-Encoding", "gzip");
test.set("Content-Type", "application/json");
test.write(Buffer.from("1f8b080000000000000bedc1010d000000c2a0f74f6d0f071400000000000000", "hex"));
test.write(Buffer.from("0000000000000000000000000000000000000000000000000000000000000000", "hex"));
test.write(Buffer.from("0000000000000000004f0625b3b71650c30000", "hex"));
test.expect(413, done);
});
});
});
function createApp(options?) {
var app = express();
app.use(express.json(options));
app.use(function (err, req, res, next) {
// console.log(err)
res.status(err.status || 500);
res.send(
String(
req.headers["x-error-property"] ? err[req.headers["x-error-property"]] : "[" + err.type + "] " + err.message,
),
);
});
app.post("/", function (req, res) {
res.json(req.body);
});
return app;
}
function parseError(str) {
try {
JSON.parse(str);
throw new SyntaxError("strict violation");
} catch (e) {
return e.message;
}
}
function shouldContainInBody(str) {
return function (res) {
assert.ok(res.text.indexOf(str) !== -1, "expected '" + res.text + "' to contain '" + str + "'");
};
}