From d04be35e204ed269337c1966270f67012f9d8ccc Mon Sep 17 00:00:00 2001 From: Bhavya Dhiman Date: Mon, 16 Mar 2026 05:01:39 +0530 Subject: [PATCH] achieve 100% test coverage in expressjs --- test/app.listen.js | 14 +++ test/req.is.js | 16 ++++ test/res.append.js | 16 ++++ test/res.jsonp.js | 22 +++++ test/res.redirect.js | 42 +++++++++ test/res.sendFile.guards.js | 168 ++++++++++++++++++++++++++++++++++++ test/view.js | 125 +++++++++++++++++++++++++++ 7 files changed, 403 insertions(+) create mode 100644 test/res.sendFile.guards.js create mode 100644 test/view.js diff --git a/test/app.listen.js b/test/app.listen.js index 3ef94ff184a..3d1bd3d27e5 100644 --- a/test/app.listen.js +++ b/test/app.listen.js @@ -41,6 +41,20 @@ describe('app.listen()', function(){ const server = app.listen(); server.close(done); }); + + it('should not invoke callback more than once', function (done) { + var app = express() + var count = 0 + var server = app.listen(0, function () { + count++ + server.emit('error', new Error('boom')) + setImmediate(function () { + assert.strictEqual(count, 1) + server.close(done) + }) + }) + }) + it('server.address() gives a { address, port, family } object', function (done) { const app = express(); const server = app.listen(0, () => { diff --git a/test/req.is.js b/test/req.is.js index c5904dd600a..d1e44e0aca0 100644 --- a/test/req.is.js +++ b/test/req.is.js @@ -166,4 +166,20 @@ describe('req.is()', function () { .expect(200, '"application/json"', done) }) }) + + describe('when given an array of types', function () { + it('should not flatten the argument', function (done) { + var app = express() + + app.use(function (req, res) { + res.json(req.is(['text/*', 'application/json'])) + }) + + request(app) + .post('/') + .type('application/json') + .send('{}') + .expect(200, '"application/json"', done) + }) + }) }) diff --git a/test/res.append.js b/test/res.append.js index 2dd17a3a1fb..3058ce92ee4 100644 --- a/test/res.append.js +++ b/test/res.append.js @@ -82,6 +82,22 @@ describe('res', function () { .end(done) }) + it('should append an array to an existing single value', function (done) { + var app = express() + + app.use(function (req, res) { + res.set('Set-Cookie', 'foo=bar') + res.append('Set-Cookie', ['fizz=buzz', 'pet=tobi']) + res.end() + }) + + request(app) + .get('/') + .expect(200) + .expect(shouldHaveHeaderValues('Set-Cookie', ['foo=bar', 'fizz=buzz', 'pet=tobi'])) + .end(done) + }) + it('should work together with res.cookie', function (done) { var app = express() diff --git a/test/res.jsonp.js b/test/res.jsonp.js index e043a3b07f2..9b1d9777847 100644 --- a/test/res.jsonp.js +++ b/test/res.jsonp.js @@ -215,6 +215,28 @@ describe('res', function(){ .expect('Content-Type', 'text/javascript; charset=utf-8') .expect(200, /cb\(42\)/, done) }) + + it('should handle non-string stringify return values', function (done) { + var app = express() + + app.use(function (req, res) { + var original = JSON.stringify + JSON.stringify = function () { + return 42 + } + + try { + res.jsonp('ignored') + } finally { + JSON.stringify = original + } + }) + + request(app) + .get('/?callback=cb') + .expect('Content-Type', 'text/javascript; charset=utf-8') + .expect(200, /cb\(42\)/, done) + }) }) describe('when given an array', function () { diff --git a/test/res.redirect.js b/test/res.redirect.js index 8d2b164e4a2..25060cca191 100644 --- a/test/res.redirect.js +++ b/test/res.redirect.js @@ -211,4 +211,46 @@ describe('res', function(){ .end(done) }) }) + + describe('when given invalid arguments', function () { + it('should handle missing url argument', function (done) { + var app = express() + + app.use(function (req, res) { + res.redirect('') + }) + + request(app) + .get('/') + .expect(302) + .expect('Location', '') + .end(done) + }) + + it('should coerce when url is not a string', function (done) { + var app = express() + + app.use(function (req, res) { + res.redirect(42) + }) + + request(app) + .get('/') + .expect(302) + .expect('Location', '42') + .end(done) + }) + + it('should fail when status is not a number', function (done) { + var app = express() + + app.use(function (req, res) { + res.redirect('302', '/somewhere') + }) + + request(app) + .get('/') + .expect(500, done) + }) + }) }) diff --git a/test/res.sendFile.guards.js b/test/res.sendFile.guards.js new file mode 100644 index 00000000000..1b00201ec58 --- /dev/null +++ b/test/res.sendFile.guards.js @@ -0,0 +1,168 @@ +'use strict' + +var assert = require('node:assert') +var EventEmitter = require('node:events').EventEmitter +var Module = require('node:module') + +var responseModulePath = require.resolve('../lib/response') + +function loadMockedResponse(sendMock, onFinishedMock) { + var originalLoad = Module._load + var savedCache = require.cache[responseModulePath] + + delete require.cache[responseModulePath] + + Module._load = function (request, parent, isMain) { + if (request === 'send') return sendMock + if (request === 'on-finished') return onFinishedMock + return originalLoad.apply(this, arguments) + } + + var mocked = require('../lib/response') + + Module._load = originalLoad + + if (savedCache) { + require.cache[responseModulePath] = savedCache + } else { + delete require.cache[responseModulePath] + } + + return mocked +} + +function createMockRes(response) { + var res = Object.create(response) + res.req = { + next: function () {} + } + res.app = { + enabled: function () { + return true + } + } + res.setHeader = function () {} + return res +} + +function runScenario(setup, done) { + var callbackCount = 0 + var callbackErr + var file + + function sendMock() { + file = new EventEmitter() + file.pipe = function (res) { + setup.pipe(file, res) + } + return file + } + + function onFinishedMock(res, onfinish) { + setup.onFinished(file, onfinish) + } + + var response = loadMockedResponse(sendMock, onFinishedMock) + var res = createMockRes(response) + + response.sendFile.call(res, '/tmp/fake.txt', function (err) { + callbackCount++ + callbackErr = err || null + setup.onCallback && setup.onCallback(err) + }) + + setImmediate(function () { + setup.assert(callbackCount, callbackErr) + done() + }) +} + +describe('res.sendFile() internal guards', function () { + it('should ignore ECONNRESET finish after already ended (onaborted done-guard)', function (done) { + runScenario({ + pipe: function (file) { + file.emit('end') + }, + onFinished: function (file, onfinish) { + setImmediate(function () { + onfinish({ code: 'ECONNRESET' }) + }) + }, + assert: function (count, err) { + assert.strictEqual(count, 1) + assert.strictEqual(err, null) + } + }, done) + }) + + it('should ignore directory event after error (ondirectory done-guard)', function (done) { + runScenario({ + pipe: function (file) { + file.emit('error', new Error('boom')) + file.emit('directory') + }, + onFinished: function () {}, + assert: function (count, err) { + assert.strictEqual(count, 1) + assert.ok(err) + } + }, done) + }) + + it('should ignore error event after end (onerror done-guard)', function (done) { + runScenario({ + pipe: function (file) { + file.emit('end') + file.emit('error', new Error('late error')) + }, + onFinished: function () {}, + assert: function (count, err) { + assert.strictEqual(count, 1) + assert.strictEqual(err, null) + } + }, done) + }) + + it('should ignore end event after error (onend done-guard)', function (done) { + runScenario({ + pipe: function (file) { + file.emit('error', new Error('boom')) + file.emit('end') + }, + onFinished: function () {}, + assert: function (count, err) { + assert.strictEqual(count, 1) + assert.ok(err) + } + }, done) + }) + + it('should handle non-ECONNRESET finish errors via onerror', function (done) { + runScenario({ + pipe: function () {}, + onFinished: function (file, onfinish) { + onfinish({ code: 'EPIPE' }) + }, + assert: function (count, err) { + assert.strictEqual(count, 1) + assert.ok(err) + assert.strictEqual(err.code, 'EPIPE') + } + }, done) + }) + + it('should skip finish completion when done before setImmediate', function (done) { + runScenario({ + pipe: function (file) { + file.emit('end') + }, + onFinished: function (file, onfinish) { + onfinish() + }, + assert: function (count, err) { + assert.strictEqual(count, 1) + assert.strictEqual(err, null) + } + }, done) + }) +}) diff --git a/test/view.js b/test/view.js new file mode 100644 index 00000000000..5cead278746 --- /dev/null +++ b/test/view.js @@ -0,0 +1,125 @@ +'use strict' + +var assert = require('node:assert') +var express = require('..') +var path = require('node:path') +var request = require('supertest') +var tmpl = require('./support/tmpl') + +describe('View', function () { + it('should handle missing options object', function () { + assert.throws(function () { + new View('name') + }, /No default engine was specified and no extension was provided\./) + }) + + describe('integration', function () { + it('should throw error via app.render() without view engine or extension', function (done) { + var app = express() + + try { + app.render('user', function (err) { + done(new Error('Should have thrown')) + }) + done(new Error('Should have thrown')) + } catch (err) { + assert.match(err.message, /No default engine was specified and no extension was provided\./) + done() + } + }) + + it('should throw error via res.render() without view engine or extension', function (done) { + var app = express() + + app.use(function (req, res) { + res.render('user') + }) + + request(app) + .get('/') + .expect(500, /No default engine was specified and no extension was provided\./, done) + }) + + it('should throw error for non-existent view engine', function (done) { + var app = express() + + app.set('view engine', 'unknown') + app.set('views', path.join(__dirname, 'fixtures')) + + try { + app.render('user', function (err) { + done(new Error('Should have thrown')) + }) + done(new Error('Should have thrown')) + } catch (err) { + assert.match(err.message, /Cannot find module/) + done() + } + }) + + it('should throw error via res.render() for non-existent view engine', function (done) { + var app = express() + + app.set('view engine', 'unknown') + app.set('views', path.join(__dirname, 'fixtures')) + + app.use(function (req, res) { + res.render('user') + }) + + request(app) + .get('/') + .expect(500, /Cannot find module/, done) + }) + + it('should successfully render via res.render() with valid engine', function (done) { + var app = express() + + app.engine('tmpl', tmpl) + app.set('view engine', 'tmpl') + app.set('views', path.join(__dirname, 'fixtures')) + app.locals.user = { name: 'tobi' } + + app.use(function (req, res) { + res.render('user') + }) + + request(app) + .get('/') + .expect('

tobi

', done) + }) + + it('should reuse cached engine on second render', function (done) { + var app = express() + + app.engine('tmpl', tmpl) + app.set('view engine', 'tmpl') + app.set('views', path.join(__dirname, 'fixtures')) + app.locals.user = { name: 'loki' } + + var renderCount = 0 + app.use(function (req, res) { + if (renderCount === 0) { + renderCount++ + res.render('user', function (err, html) { + if (err) return res.send(500) + // render again to test cached engine + res.render('user', function (err, html2) { + if (err) return res.send(500) + res.send(html2) + }) + }) + } + }) + + request(app) + .get('/') + .expect('

loki

', done) + }) + }) +}) + +function View(name) { + var View = require('../lib/view') + return new View(name) +}