Skip to content

Commit 8bb68bb

Browse files
committed
fix: prevent express errors when headers have already been sent
1 parent fecf737 commit 8bb68bb

13 files changed

+206
-69
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"test": "meteor npm run setup-test && meteor npm run test:once",
2626
"test:once": "cd someapp && BABEL_ENV=COVERAGE TEST_BROWSER_DRIVER=puppeteer COVERAGE_VERBOSE=1 COVERAGE=1 COVERAGE_OUT_LCOVONLY=1 COVERAGE_APP_FOLDER=$(pwd)/ meteor test-packages --once --driver-package meteortesting:mocha ./packages/meteor-coverage",
2727
"test:watch": "cd someapp && BABEL_ENV=COVERAGE TEST_WATCH=1 COVERAGE_VERBOSE=1 COVERAGE=1 COVERAGE_APP_FOLDER=$(pwd)/ meteor test-packages --driver-package meteortesting:mocha ./packages/meteor-coverage",
28-
"test:headless": "cd someapp && BABEL_ENV=COVERAGE TEST_BROWSER_DRIVER=puppeteer COVERAGE_VERBOSE=1 COVERAGE=1 COVERAGE_OUT_LCOVONLY=1 COVERAGE_APP_FOLDER=$(pwd)/ meteor test-packages --driver-package meteortesting:mocha ./packages/meteor-coverage",
28+
"test:headless": "cd someapp && BABEL_ENV=COVERAGE TEST_BROWSER_DRIVER=puppeteer COVERAGE=1 COVERAGE_APP_FOLDER=$(pwd)/ meteor test-packages --driver-package meteortesting:mocha ./packages/meteor-coverage",
2929
"start": "meteor npm run lint:fix & meteor npm run test:watch",
3030
"lint": "eslint .",
3131
"lint:fix": "eslint --fix .",

server/handlers.js

+29-13
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import ReportService from './report/report-service';
44
import fs from 'node:fs';
55
import path from 'node:path';
66
import Log from './context/log';
7+
import { Response } from './services/response';
78

89
showCoverage = function (params, req, res, next) {
910
let options = {
@@ -24,20 +25,20 @@ getAsset = function (params, req, res, next) {
2425
fs.readFile(assetsDir + '/vendor/' + filename, function (err, fileContent) {
2526
/* istanbul ignore else */
2627
if (err) {
27-
console.error(err);
28+
Log.error(err);
2829
return next();
2930
}
30-
res.json(fileContent);
31+
Response.send({ res, message: fileContent });
3132
});
3233
});
3334
} else {
3435
fs.readFile(assetsDir + '/' + filename, function (err, fileContent) {
3536
/* istanbul ignore else */
3637
if (err) {
37-
console.error(err);
38+
Log.error(err);
3839
return next();
3940
}
40-
res.json(fileContent);
41+
Response.send({ res, message: fileContent });
4142
});
4243
}
4344
});
@@ -47,8 +48,11 @@ addClientCoverage = function (params, req, res, next) {
4748
var body = req.body;
4849
/* istanbul ignore else */
4950
if (!body) {
50-
res.status(400);
51-
return res.end();
51+
return Response.send({
52+
res,
53+
status: 400,
54+
end: true
55+
});
5256
}
5357

5458
var clientCoverage;
@@ -60,10 +64,16 @@ addClientCoverage = function (params, req, res, next) {
6064
}
6165
if (clientCoverage) {
6266
Core.mergeCoverageWith(clientCoverage);
63-
res.json({ type: 'success' });
67+
Response.send({
68+
res,
69+
json: { type: 'success' }
70+
});
6471
} else {
65-
res.status(400);
66-
res.send('Nothing has been imported');
72+
Response.send({
73+
res,
74+
status: 400,
75+
message: 'Nothing has been imported'
76+
});
6777
}
6878
};
6979

@@ -76,17 +86,23 @@ exportFile = function (params, req, res, next) {
7686
reportService.generateReport(res, type, {});
7787
} catch (e) {
7888
Log.error('Failed to export', e, e.stack);
79-
res.status(400);
80-
res.send('Nothing has been export');
89+
Response.send({
90+
res,
91+
status: 400,
92+
message: 'Nothing has been export'
93+
});
8194
}
8295
};
8396
importCoverage = function (params, req, res, next) {
8497
try {
8598
Core.importCoverage(res);
8699
} catch (e) {
87100
Log.error('Failed to import', e, e.stack);
88-
res.status(400);
89-
res.send('No file has been import');
101+
Response.send({
102+
res,
103+
status: 400,
104+
message: 'No file has been import'
105+
});
90106
}
91107
};
92108

server/report/report-coverage.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import fs from 'node:fs';
2+
import { Response } from '../services/response';
23

34
export default class {
45
constructor(res, options) {
@@ -15,9 +16,15 @@ export default class {
1516
fs.writeFile(reportPath, coverageReport, function (err) {
1617
/* istanbul ignore else */
1718
if (err) {
18-
instance.res.json({ type: 'failed', message: 'failed to write report file: ' + reportPath });
19+
Response.send({
20+
res: instance.res,
21+
json: { type: 'failed', message: 'failed to write report file: ' + reportPath }
22+
});
1923
} else {
20-
instance.res.json({ type: 'success' });
24+
Response.send({
25+
res: instance.res,
26+
json: { type: 'success' }
27+
});
2128
}
2229
});
2330
}

server/report/report-generic.js

+17-9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Core from './../services/core';
33
import ReportCommon from './report-common';
44
import Conf from '../context/conf';
55
import Log from '../context/log';
6+
import { Response } from '../services/response';
67

78
const ReportImpl = Npm.require('istanbul-reports');
89

@@ -22,26 +23,33 @@ export default class {
2223

2324
generate() {
2425
const coverage = Core.getCoverageObject();
25-
let childs = CoverageData.getLcovonlyReport(coverage);
26+
let children = CoverageData.getLcovonlyReport(coverage);
2627
this.report.onStart(null, this.context);
2728
/* istanbul ignore else */
28-
if (childs.length === 0) {
29-
this.res.set('Content-type', 'text/plain');
30-
this.res.status(500);
31-
return this.res.json({'type':'No coverage to export'});
29+
if (children.length === 0) {
30+
return Response.send({
31+
res: this.res,
32+
status: 500,
33+
type: 'application/json',
34+
json: {'type':'No coverage to export'}
35+
});
3236
}
3337

34-
this.writeFile(childs);
35-
this.res.json({ type: 'success' });
38+
this.writeFile(children);
39+
Response.send({
40+
res: this.res,
41+
type: 'application/json',
42+
json: { type: 'success' }
43+
});
3644
}
3745

3846
writeFile(children) {
3947
for (let i = 0; i < children.length; i++) {
4048
// Remove the COVERAGE_APP_FOLDER from the filepath
4149
try {
4250
children[i].fileCoverage.data.path = children[i].fileCoverage.data.path.replace(Conf.COVERAGE_APP_FOLDER, '');
43-
} catch {
44-
// eslint-disable no-empty
51+
} catch (e) {
52+
Log.error(e);
4553
}
4654
this.report.onDetail(children[i]);
4755
}

server/report/report-html.js

+17-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import fs from 'node:fs';
44
import path from 'node:path';
55
import ReportCommon from './report-common';
66
import Log from './../context/log';
7+
import { Response } from '../services/response';
8+
79
const Report = Npm.require('istanbul-lib-report'),
810
ReportImpl = Npm.require('istanbul-reports');
911

@@ -54,11 +56,17 @@ export default class {
5456

5557
/* istanbul ignore else */
5658
if (!(coverage && Object.keys(coverage).length > 0)) {
57-
this.res.status(500);
58-
return this.res.json({
59-
type:'failed',
60-
message: 'No coverage information have been collected'
59+
return Response.send({
60+
res: this.res,
61+
status: 500,
62+
type: 'application/json',
63+
json: {
64+
type:'failed',
65+
status: 500,
66+
message: 'No coverage information have been collected'
67+
}
6168
});
69+
6270
}
6371
let root = CoverageData.getTreeReport(coverage);
6472
let filepath = path.join(folderPath, 'index.html');
@@ -77,7 +85,11 @@ export default class {
7785
report.onDetail(fileReport, reportCtx);
7886
});
7987

80-
this.res.json({ type: 'success' });
88+
Response.send({
89+
res: this.res,
90+
type: 'application/json',
91+
json: { type: 'success' }
92+
});
8193
}
8294

8395
copyStatic() {

server/report/report-http.js

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import CoverageData from '../services/coverage-data';
22
import Conf from '../context/conf';
33
import Core from '../services/core';
4+
import { Response } from '../services/response';
45
// If we change Npm.require('istanbul-reports') into import a from 'istanbul-reports'
56
// the __dirname change and the istanbul dependency fails
67
// See istanbul-reports
@@ -43,8 +44,11 @@ export default class {
4344
var coverage = Core.getCoverageObject();
4445
/* istanbul ignore else */
4546
if (!(coverage && Object.keys(coverage).length > 0)) {
46-
this.res.set('Content-type', 'text/plain');
47-
return this.res.send('No coverage information has been collected');
47+
return Response.send({
48+
res: this.res,
49+
type: 'text/plain',
50+
message: 'No coverage information has been collected'
51+
});
4852
}
4953
this.res.set('Content-type', 'text/html');
5054
this.alterFS(this.res);
@@ -79,7 +83,9 @@ export default class {
7983
// istanbul-reports expect to save HTML report to the file system and not over network
8084
alterFS(res) {
8185
res.close = function () {
82-
this.end();
86+
if (!res.headersSent) {
87+
this.end();
88+
}
8389
};
8490
}
8591

server/report/report-json-summary.js

+25-15
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import Conf from './../context/conf';
22
import CoverageData from './../services/coverage-data';
33
import Core from './../services/core';
44
import ReportCommon from './report-common';
5+
import Log from '../context/log';
6+
import { Response } from '../services/response';
7+
58
const ReportImpl = Npm.require('istanbul-reports');
69

710
export default class {
@@ -18,28 +21,35 @@ export default class {
1821

1922
generate() {
2023
const coverage = Core.getCoverageObject();
21-
let childs = CoverageData.getLcovonlyReport(coverage);
24+
let children = CoverageData.getLcovonlyReport(coverage);
2225
this.report.onStart(null, this.context);
23-
/* istanbul ignore else */
24-
this.res.set('Content-type', 'application/json');
2526

26-
if (childs.length === 0) {
27-
this.res.status(500);
28-
return this.res.json({'type':'No coverage to export'});
29-
}
30-
this.writeFile(childs);
31-
this.res.json({ type: 'success' });
27+
if (children.length === 0) {
28+
return Response.send({
29+
res: this.res,
30+
status: 500,
31+
type: 'application/json',
32+
json: {'type':'No coverage to export'}
33+
});
34+
}
35+
this.writeFile(children);
36+
Response.send({
37+
res: this.res,
38+
type: 'application/json',
39+
json: { type: 'success' }
40+
});
41+
3242
}
3343

34-
writeFile (childs) {
35-
for (let i = 0; i < childs.length; i++) {
44+
writeFile (children) {
45+
for (let i = 0; i < children.length; i++) {
3646
// Remove the COVERAGE_APP_FOLDER from the filepath
3747
try {
38-
childs[i].fileCoverage.data.path = childs[i].fileCoverage.data.path.replace(Conf.COVERAGE_APP_FOLDER, '');
39-
} catch {
40-
// eslint-disable no-empty
48+
children[i].fileCoverage.data.path = children[i].fileCoverage.data.path.replace(Conf.COVERAGE_APP_FOLDER, '');
49+
} catch (e) {
50+
Log.error(e);
4151
}
42-
this.report.onDetail(childs[i]);
52+
this.report.onDetail(children[i]);
4353
}
4454
///Todo: not working
4555
//this.report.onSummary(childs);

server/report/report-remap.js

+10-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import ReportCommon from './report-common';
33
import IstanbulGenericReporter from './report-generic';
44
import path from 'node:path';
55
import Log from '../context/log';
6+
import { Response } from '../services/response';
7+
68
const remapIstanbul = Npm.require('remap-istanbul');
79
const MemoryStore = Npm.require('istanbul/lib/store/memory');
810

@@ -59,11 +61,15 @@ export default class {
5961
this.remapWrapper(this.pathJSON, reports, this.options)
6062
.catch(e => {
6163
Log.error(e);
62-
res.status(500);
63-
res.send(e.message);
64+
Response.send({
65+
res,
66+
status: 500,
67+
type: 'text/plain',
68+
message: e.message
69+
});
6470
})
6571
.finally(() => {
66-
// Restore previous working directory
72+
// Restore previous working directory
6773
process.chdir(cwd);
6874
});
6975
}
@@ -82,7 +88,7 @@ export default class {
8288
}
8389

8490
let p = Object.keys(reports).map((reportType) => {
85-
let reportOptions = Object.assign({}, this.options, {verbose: reportType !== 'html'});
91+
let reportOptions = Object.assign({}, options, {verbose: reportType !== 'html'});
8692
return remapIstanbul.writeReport(collector, reportType, reportOptions, reports[reportType], sourceStore);
8793
});
8894

server/report/report-service.js

+12-6
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import ReportCoverage from './report-coverage';
99
import ReportRemap from './report-remap';
1010
import TextSummary from './report-text-summary';
1111
import path from 'node:path';
12-
import fs from 'node:fs';
12+
import { Response } from '../services/response';
1313

1414
export default class {
1515
generateReport(res, type, options) {
@@ -88,15 +88,21 @@ export default class {
8888
}
8989
default:
9090
Log.error('Failed to export - this type is not implemented yet');
91-
res.status(400);
92-
res.json({ type: 'This type [\' + type + \'] is not supported' });
91+
Response.send({
92+
res,
93+
status: 400,
94+
json: { type: 'This type [\' + type + \'] is not supported' }
95+
});
9396
return;
9497
}
9598
} catch (e) {
9699
Log.error('ReportService failed while creating report type [', type, ']');
97-
console.error(e, e.stack);
98-
res.status(400);
99-
res.json({ type: 'error','message':'Unexpected error'});
100+
Log.error(e);
101+
Response.send({
102+
res,
103+
status: 400,
104+
json: { type: 'error','message': `Unexpected error. ${e.message}`}
105+
});
100106
}
101107
}
102108
addFileToOptions(options, filename) {

0 commit comments

Comments
 (0)