@contrast/route-coverage 1.21.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.test.js +129 -0
- package/lib/install/express.test.js +418 -0
- package/lib/install/fastify.test.js +203 -0
- package/lib/install/hapi.test.js +125 -0
- package/lib/install/koa.test.js +152 -0
- package/lib/install/restify.test.js +92 -0
- package/package.json +2 -2
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { expect } = require('chai');
|
|
4
|
+
const sinon = require('sinon');
|
|
5
|
+
const proxyquire = require('proxyquire');
|
|
6
|
+
const { Event } = require('@contrast/common');
|
|
7
|
+
const mocks = require('@contrast/test/mocks');
|
|
8
|
+
const { initAssessFixture } = require('@contrast/test/fixtures');
|
|
9
|
+
const { installsComponents } = require('@contrast/test/utils');
|
|
10
|
+
|
|
11
|
+
const MODULES = ['express', 'fastify', 'hapi', 'koa'];
|
|
12
|
+
|
|
13
|
+
describe('route coverage', function () {
|
|
14
|
+
let core, simulateRequestScope, routeCoverage;
|
|
15
|
+
|
|
16
|
+
beforeEach(function () {
|
|
17
|
+
({ core, simulateRequestScope } = initAssessFixture());
|
|
18
|
+
|
|
19
|
+
sinon.spy(core.messages, 'emit');
|
|
20
|
+
|
|
21
|
+
const moduleMock = (moduleName) => (deps) => {
|
|
22
|
+
deps.routeCoverage[moduleName] = mocks.installable();
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
routeCoverage = proxyquire('.', {
|
|
26
|
+
'./install/express': moduleMock('express'),
|
|
27
|
+
'./install/fastify': moduleMock('fastify'),
|
|
28
|
+
'./install/hapi': moduleMock('hapi'),
|
|
29
|
+
'./install/koa': moduleMock('koa'),
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('installs its components', function () {
|
|
34
|
+
routeCoverage(core).install();
|
|
35
|
+
installsComponents(core.routeCoverage, MODULES);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('does not install when not enabled', function () {
|
|
39
|
+
core.config.agent.route_coverage.enable = false;
|
|
40
|
+
routeCoverage(core);
|
|
41
|
+
expect(core).not.to.have.property('routeCoverage');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('.discoveryFinished()', function () {
|
|
45
|
+
beforeEach(function () {
|
|
46
|
+
routeCoverage(core).install();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('emits an event when discovery is finished', function () {
|
|
50
|
+
const eventA = { signature: 'hello', url: 'url', method: 'get' };
|
|
51
|
+
const eventB = { signature: 'hello', url: 'url', method: 'post' };
|
|
52
|
+
core.routeCoverage.discover(eventA);
|
|
53
|
+
core.routeCoverage.discover(eventA); // check that we dedupe discovery.
|
|
54
|
+
core.routeCoverage.discover(eventB);
|
|
55
|
+
core.routeCoverage.discoveryFinished();
|
|
56
|
+
|
|
57
|
+
expect(core.messages.emit).to.have.been.calledWith(
|
|
58
|
+
Event.ROUTE_COVERAGE_DISCOVERY_FINISHED,
|
|
59
|
+
[eventA, eventB],
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('.observe()', function () {
|
|
65
|
+
beforeEach(function () {
|
|
66
|
+
routeCoverage(core).install();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('logs debug messages when a route is missing on observation', function () {
|
|
70
|
+
const info = { url: 'url', method: 'get' };
|
|
71
|
+
core.routeCoverage.observe(info);
|
|
72
|
+
expect(core.logger.debug).to.have.been.calledOnceWithExactly(
|
|
73
|
+
info,
|
|
74
|
+
'unable to observe undiscovered route',
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('emits an event on route observation', function () {
|
|
79
|
+
const event = {
|
|
80
|
+
signature: 'hello',
|
|
81
|
+
url: 'url',
|
|
82
|
+
method: 'get',
|
|
83
|
+
sourceInfo: undefined
|
|
84
|
+
};
|
|
85
|
+
core.routeCoverage.discover(event);
|
|
86
|
+
core.routeCoverage.observe({ url: 'url', method: 'get' });
|
|
87
|
+
|
|
88
|
+
expect(core.messages.emit).to.have.been.calledWith(
|
|
89
|
+
Event.ROUTE_COVERAGE_OBSERVATION,
|
|
90
|
+
event,
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('emits an event on route observation and appends sourceInfo when in request scope', function () {
|
|
95
|
+
const event = {
|
|
96
|
+
signature: 'hello',
|
|
97
|
+
url: 'url',
|
|
98
|
+
method: 'get',
|
|
99
|
+
};
|
|
100
|
+
core.routeCoverage.discover(event);
|
|
101
|
+
|
|
102
|
+
simulateRequestScope(() => {
|
|
103
|
+
core.routeCoverage.observe({ url: 'url', method: 'get' });
|
|
104
|
+
const { sourceInfo, route } = core.scopes.sources.getStore();
|
|
105
|
+
expect(sourceInfo).to.be.ok;
|
|
106
|
+
expect(route).to.eql({ method: 'get', signature: 'hello', url: 'url' });
|
|
107
|
+
expect(core.messages.emit).to.have.been.calledWith(
|
|
108
|
+
Event.ROUTE_COVERAGE_OBSERVATION,
|
|
109
|
+
sinon.match({ ...event, sourceInfo })
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('skip adding normalizedUrl if assess store does not exist', function () {
|
|
115
|
+
const event = {
|
|
116
|
+
signature: 'hello',
|
|
117
|
+
url: 'url',
|
|
118
|
+
method: 'get',
|
|
119
|
+
};
|
|
120
|
+
core.routeCoverage.discover(event);
|
|
121
|
+
|
|
122
|
+
simulateRequestScope(() => {
|
|
123
|
+
core.routeCoverage.observe({ url: 'url', method: 'get' });
|
|
124
|
+
const store = core.scopes.sources.getStore();
|
|
125
|
+
expect(store.assess).to.be.undefined;
|
|
126
|
+
}, {});
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { expect } = require('chai');
|
|
4
|
+
const sinon = require('sinon');
|
|
5
|
+
const scopes = require('@contrast/scopes');
|
|
6
|
+
const patcher = require('@contrast/patcher');
|
|
7
|
+
const mocks = require('@contrast/test/mocks');
|
|
8
|
+
|
|
9
|
+
const METHODS = [
|
|
10
|
+
'all',
|
|
11
|
+
'get',
|
|
12
|
+
'post',
|
|
13
|
+
'put',
|
|
14
|
+
'delete',
|
|
15
|
+
'patch',
|
|
16
|
+
'options',
|
|
17
|
+
'head',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
describe('route-coverage express', function () {
|
|
21
|
+
let core, http, Server, application, Router, Layer, express, fn, framework;
|
|
22
|
+
|
|
23
|
+
beforeEach(function () {
|
|
24
|
+
core = mocks.core();
|
|
25
|
+
core.logger = mocks.logger();
|
|
26
|
+
core.routeCoverage = mocks.routeCoverage();
|
|
27
|
+
core.scopes = scopes(core);
|
|
28
|
+
core.depHooks = mocks.depHooks();
|
|
29
|
+
core.patcher = patcher(core);
|
|
30
|
+
|
|
31
|
+
application = () => application;
|
|
32
|
+
Router = () => Router;
|
|
33
|
+
Layer = () => Layer;
|
|
34
|
+
Layer.handle = sinon.stub();
|
|
35
|
+
|
|
36
|
+
express = () => application;
|
|
37
|
+
framework = 'express';
|
|
38
|
+
|
|
39
|
+
Router.stack = [];
|
|
40
|
+
Router.handle = sinon.stub();
|
|
41
|
+
Router.use = (prefix, router) => {
|
|
42
|
+
Router.stack.push({ handle: router, name: 'router', regexp: { source: `\\${prefix}` } });
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
express.application = application;
|
|
46
|
+
express.application.use = (prefix, router) => {
|
|
47
|
+
if (router?.use) router.use(prefix, router);
|
|
48
|
+
return { _router: Router };
|
|
49
|
+
};
|
|
50
|
+
express.application._router = Router;
|
|
51
|
+
express.Router = Router;
|
|
52
|
+
|
|
53
|
+
METHODS.forEach((method) => {
|
|
54
|
+
application[method] = () => {
|
|
55
|
+
Router.stack.push(Layer);
|
|
56
|
+
return { _router: Router };
|
|
57
|
+
};
|
|
58
|
+
Router[method] = (path, fn) => {
|
|
59
|
+
Layer.regexp = { source: `\\${path}` };
|
|
60
|
+
Layer.route = {
|
|
61
|
+
path,
|
|
62
|
+
stack: [
|
|
63
|
+
{
|
|
64
|
+
method
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
methods: {
|
|
68
|
+
_all: false
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
if (fn) Router.stack.push(Layer);
|
|
72
|
+
return Router;
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
Server = function () { };
|
|
76
|
+
Server.prototype.listen = sinon.stub();
|
|
77
|
+
http = {
|
|
78
|
+
Server,
|
|
79
|
+
createServer() {
|
|
80
|
+
return new Server();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
fn = sinon.stub();
|
|
85
|
+
|
|
86
|
+
core.depHooks.resolve.withArgs({ name: 'express' }).yields(express);
|
|
87
|
+
core.depHooks.resolve.withArgs({ name: 'http' }).yields(http);
|
|
88
|
+
|
|
89
|
+
require('./express')(core).install();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('app', function () {
|
|
93
|
+
METHODS.forEach((method) => {
|
|
94
|
+
describe(`${method}`, function () {
|
|
95
|
+
it('does not report a non-existent route', function () {
|
|
96
|
+
const app = express();
|
|
97
|
+
app[method]();
|
|
98
|
+
expect(core.routeCoverage.discover).not.to.have.been.called;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('does not report an invalid route', function () {
|
|
102
|
+
const app = express();
|
|
103
|
+
app[method](42);
|
|
104
|
+
expect(core.routeCoverage.discover).not.to.have.been.called;
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('does not report a route missing a method', function () {
|
|
108
|
+
const app = express();
|
|
109
|
+
app[method]('/test/route');
|
|
110
|
+
expect(core.routeCoverage.discover).not.to.have.been.called;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('discovers a route', function () {
|
|
114
|
+
const app = express();
|
|
115
|
+
app[method]('/test/route', fn);
|
|
116
|
+
|
|
117
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
118
|
+
signature: `App.${method}('/test/route', [Function])`,
|
|
119
|
+
url: '/test/route',
|
|
120
|
+
normalizedUrl: '/test/route',
|
|
121
|
+
method,
|
|
122
|
+
framework
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('discovers an array route', function () {
|
|
127
|
+
const app = express();
|
|
128
|
+
app[method](['/test/foo', '/test/bar'], fn);
|
|
129
|
+
|
|
130
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
131
|
+
signature: `App.${method}('/[/test/foo,/test/bar]', [Function])`,
|
|
132
|
+
url: '/[/test/foo,/test/bar]',
|
|
133
|
+
normalizedUrl: '/[/test/foo,/test/bar]',
|
|
134
|
+
method,
|
|
135
|
+
framework
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('discovers a regEx route', function () {
|
|
140
|
+
const app = express();
|
|
141
|
+
app[method](/f*o/, fn);
|
|
142
|
+
|
|
143
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
144
|
+
signature: `App.${method}('/{f*o}', [Function])`,
|
|
145
|
+
url: '/{f*o}',
|
|
146
|
+
normalizedUrl: '/{f*o}',
|
|
147
|
+
method,
|
|
148
|
+
framework
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('does not discover and App.use route with no path and no router', function () {
|
|
155
|
+
const app = express();
|
|
156
|
+
app.use(fn);
|
|
157
|
+
expect(core.routeCoverage.discover).not.have.been.called;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('discovers an App.use route', function () {
|
|
161
|
+
const app = express();
|
|
162
|
+
app.use('/foo', fn);
|
|
163
|
+
|
|
164
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
165
|
+
signature: 'App.use(\'/foo\', [Function])',
|
|
166
|
+
url: '/foo',
|
|
167
|
+
normalizedUrl: '/foo',
|
|
168
|
+
method: 'use',
|
|
169
|
+
framework
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('discovers an App.use route with a non-string url', function () {
|
|
174
|
+
const app = express();
|
|
175
|
+
app.use(['/foo'], fn);
|
|
176
|
+
|
|
177
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
178
|
+
signature: 'App.use(\'/[/foo]\', [Function])',
|
|
179
|
+
url: '/[/foo]',
|
|
180
|
+
normalizedUrl: '/[/foo]',
|
|
181
|
+
method: 'use',
|
|
182
|
+
framework
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('discovers a route with middleware defined in an array', function() {
|
|
187
|
+
const app = express();
|
|
188
|
+
app.use('/foo', [fn]);
|
|
189
|
+
|
|
190
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
191
|
+
signature: 'App.use(\'/foo\', [Function])',
|
|
192
|
+
url: '/foo',
|
|
193
|
+
normalizedUrl: '/foo',
|
|
194
|
+
method: 'use',
|
|
195
|
+
framework
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('does not observe a route with an undefined method', function () {
|
|
200
|
+
const app = express();
|
|
201
|
+
app.get('/path/to/foo', fn);
|
|
202
|
+
|
|
203
|
+
express.Router.handle({
|
|
204
|
+
originalUrl: '/path/to/foo',
|
|
205
|
+
route: { path: '/path/to/foo' }
|
|
206
|
+
});
|
|
207
|
+
expect(core.routeCoverage.observe).to.not.have.been.called;
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('observes a route when a route handler is exercised', function () {
|
|
211
|
+
const app = express();
|
|
212
|
+
app.get('/path/to/foo', fn);
|
|
213
|
+
|
|
214
|
+
express.Router.stack[0].handle({
|
|
215
|
+
method: 'GET',
|
|
216
|
+
originalUrl: '/path/to/foo',
|
|
217
|
+
route: { path: '/path/to/foo' }
|
|
218
|
+
});
|
|
219
|
+
expect(core.routeCoverage.observe).to.have.been.calledWith({
|
|
220
|
+
signature: "App.get('/path/to/foo', [Function])",
|
|
221
|
+
url: '/path/to/foo',
|
|
222
|
+
normalizedUrl: '/path/to/foo',
|
|
223
|
+
method: 'get',
|
|
224
|
+
framework
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('observes a route with a regExp path', function () {
|
|
229
|
+
const app = express();
|
|
230
|
+
app.get(/.*/, fn);
|
|
231
|
+
|
|
232
|
+
express.Router.stack[0].handle({
|
|
233
|
+
method: 'GET',
|
|
234
|
+
originalUrl: '/path',
|
|
235
|
+
route: { path: /.*/ }
|
|
236
|
+
});
|
|
237
|
+
expect(core.routeCoverage.observe).to.have.been.calledWith({
|
|
238
|
+
signature: "App.get('/{.*}', [Function])",
|
|
239
|
+
url: '/path',
|
|
240
|
+
normalizedUrl: '/{.*}',
|
|
241
|
+
method: 'get',
|
|
242
|
+
framework
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('observes a route with an Array path', function () {
|
|
247
|
+
const app = express();
|
|
248
|
+
app.get(['/test/foo', '/test/bar'], fn);
|
|
249
|
+
|
|
250
|
+
express.Router.stack[0].handle({
|
|
251
|
+
method: 'GET',
|
|
252
|
+
originalUrl: '/[/test/foo,/test/bar]',
|
|
253
|
+
route: { path: ['/test/foo', '/test/bar'] }
|
|
254
|
+
});
|
|
255
|
+
expect(core.routeCoverage.observe).to.have.been.calledWith({
|
|
256
|
+
signature: "App.get('/[/test/foo,/test/bar]', [Function])",
|
|
257
|
+
url: '/[/test/foo,/test/bar]',
|
|
258
|
+
normalizedUrl: '/[/test/foo,/test/bar]',
|
|
259
|
+
method: 'get',
|
|
260
|
+
framework
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('updates route prefixes', function () {
|
|
265
|
+
const app = express();
|
|
266
|
+
const router = express.Router();
|
|
267
|
+
router.get('/foo', fn);
|
|
268
|
+
app.use('/prefix', router);
|
|
269
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
270
|
+
signature: "Router.get('/prefix/foo', [Function])",
|
|
271
|
+
url: '/prefix/foo',
|
|
272
|
+
normalizedUrl: '/prefix/foo',
|
|
273
|
+
method: 'get',
|
|
274
|
+
framework
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
describe('router', function () {
|
|
280
|
+
METHODS.forEach((method) => {
|
|
281
|
+
describe(`${method}`, function () {
|
|
282
|
+
it('does not report a non-existent route', function () {
|
|
283
|
+
const app = express();
|
|
284
|
+
const router = express.Router();
|
|
285
|
+
router[method]();
|
|
286
|
+
app.use(router);
|
|
287
|
+
expect(core.routeCoverage.discover).not.to.have.been.called;
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('does not report an invalid route', function () {
|
|
291
|
+
const app = express();
|
|
292
|
+
const router = express.Router();
|
|
293
|
+
router[method](42);
|
|
294
|
+
app.use(router);
|
|
295
|
+
expect(core.routeCoverage.discover).not.to.have.been.called;
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('does not report a route missing a handler', function () {
|
|
299
|
+
const app = express();
|
|
300
|
+
const router = express.Router();
|
|
301
|
+
router[method]('/test/route');
|
|
302
|
+
app.use(router);
|
|
303
|
+
expect(core.routeCoverage.discover).not.to.have.been.called;
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('discovers a route', function () {
|
|
307
|
+
const app = express();
|
|
308
|
+
const router = express.Router();
|
|
309
|
+
router[method]('/test/route', fn);
|
|
310
|
+
app.use(router);
|
|
311
|
+
|
|
312
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
313
|
+
signature: `Router.${method}('/test/route', [Function])`,
|
|
314
|
+
url: '/test/route',
|
|
315
|
+
normalizedUrl: '/test/route',
|
|
316
|
+
method,
|
|
317
|
+
framework
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('discovers an array route', function () {
|
|
322
|
+
const app = express();
|
|
323
|
+
const router = express.Router();
|
|
324
|
+
router[method](['/test/foo', '/test/bar'], fn);
|
|
325
|
+
app.use(router);
|
|
326
|
+
|
|
327
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
328
|
+
signature: `Router.${method}('/[/test/foo,/test/bar]', [Function])`,
|
|
329
|
+
url: '/[/test/foo,/test/bar]',
|
|
330
|
+
normalizedUrl: '/[/test/foo,/test/bar]',
|
|
331
|
+
method,
|
|
332
|
+
framework
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('discovers a regEx route', function () {
|
|
337
|
+
const app = express();
|
|
338
|
+
const router = express.Router();
|
|
339
|
+
router[method](/f*o/, fn);
|
|
340
|
+
app.use(router);
|
|
341
|
+
|
|
342
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
343
|
+
signature: `Router.${method}('/{f*o}', [Function])`,
|
|
344
|
+
url: '/{f*o}',
|
|
345
|
+
normalizedUrl: '/{f*o}',
|
|
346
|
+
method,
|
|
347
|
+
framework
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('does not observe a route with an undefined method', function () {
|
|
354
|
+
const app = express();
|
|
355
|
+
const router = express.Router();
|
|
356
|
+
router.get('/path/to/foo', fn);
|
|
357
|
+
app.use(router);
|
|
358
|
+
|
|
359
|
+
express.Router.handle({
|
|
360
|
+
originalUrl: '/path/to/foo',
|
|
361
|
+
route: { path: '/path/to/foo' }
|
|
362
|
+
});
|
|
363
|
+
expect(core.routeCoverage.observe).to.not.have.been.called;
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('observes a route when a route handler is exercised', function () {
|
|
367
|
+
const app = express();
|
|
368
|
+
const router = express.Router();
|
|
369
|
+
router.get('/path/to/foo', fn);
|
|
370
|
+
app.use(router);
|
|
371
|
+
|
|
372
|
+
express.Router.stack[0].handle({
|
|
373
|
+
method: 'GET',
|
|
374
|
+
originalUrl: '/path/to/foo',
|
|
375
|
+
route: { path: '/path/to/foo' }
|
|
376
|
+
});
|
|
377
|
+
expect(core.routeCoverage.observe).to.have.been.calledWith({
|
|
378
|
+
signature: "Router.get('/path/to/foo', [Function])",
|
|
379
|
+
url: '/path/to/foo',
|
|
380
|
+
normalizedUrl: '/path/to/foo',
|
|
381
|
+
method: 'get',
|
|
382
|
+
framework
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('updates route prefixes on nested router', function () {
|
|
387
|
+
const app = express();
|
|
388
|
+
const router = express.Router();
|
|
389
|
+
router.get('/foo', fn);
|
|
390
|
+
router.use('/prefix', router);
|
|
391
|
+
app.use(router);
|
|
392
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
393
|
+
signature: "Router.get('/prefix/foo', [Function])",
|
|
394
|
+
url: '/prefix/foo',
|
|
395
|
+
normalizedUrl: '/prefix/foo',
|
|
396
|
+
method: 'get',
|
|
397
|
+
framework
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('updates route prefixes on a non-nested router', function () {
|
|
402
|
+
const app = express();
|
|
403
|
+
const router = express.Router();
|
|
404
|
+
const router2 = express.Router();
|
|
405
|
+
router.get('/foo', fn);
|
|
406
|
+
router2.use('/prefix', router);
|
|
407
|
+
app.use(router);
|
|
408
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
409
|
+
signature: "Router.get('/prefix/foo', [Function])",
|
|
410
|
+
url: '/prefix/foo',
|
|
411
|
+
normalizedUrl: '/prefix/foo',
|
|
412
|
+
method: 'get',
|
|
413
|
+
framework
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
});
|
|
418
|
+
});
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { expect } = require('chai');
|
|
4
|
+
const sinon = require('sinon');
|
|
5
|
+
const scopes = require('@contrast/scopes');
|
|
6
|
+
const patcher = require('@contrast/patcher');
|
|
7
|
+
const mocks = require('@contrast/test/mocks');
|
|
8
|
+
const { getFastifyMethods } = require('../utils/methods');
|
|
9
|
+
|
|
10
|
+
describe('route-coverage fastify', function () {
|
|
11
|
+
let core, serverMock, framework;
|
|
12
|
+
|
|
13
|
+
beforeEach(function () {
|
|
14
|
+
core = mocks.core();
|
|
15
|
+
core.logger = mocks.logger();
|
|
16
|
+
core.routeCoverage = mocks.routeCoverage();
|
|
17
|
+
core.scopes = scopes(core);
|
|
18
|
+
core.depHooks = mocks.depHooks();
|
|
19
|
+
core.patcher = patcher(core);
|
|
20
|
+
|
|
21
|
+
serverMock = {
|
|
22
|
+
addHook: sinon.stub(),
|
|
23
|
+
route: sinon.stub(),
|
|
24
|
+
version: '4.4.0'
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
let fastify;
|
|
28
|
+
framework = 'fastify';
|
|
29
|
+
core.depHooks.resolve.callsFake((desc, cb) => {
|
|
30
|
+
fastify = cb(() => serverMock);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
require('./fastify')(core).install();
|
|
34
|
+
|
|
35
|
+
fastify();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('skips instrumenting if route options does not exist', function () {
|
|
39
|
+
serverMock.addHook.withArgs('onRoute').yield();
|
|
40
|
+
expect(core.routeCoverage.discover).not.to.have.been.called;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('skips instrumenting if there is no method provided', function () {
|
|
44
|
+
serverMock.addHook.withArgs('onRoute').yield({});
|
|
45
|
+
expect(core.routeCoverage.discover).not.to.have.been.called;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('skips instrumenting if there is no url provided', function () {
|
|
49
|
+
serverMock.addHook.withArgs('onRoute').yield({ method: 'GET' });
|
|
50
|
+
expect(core.routeCoverage.discover).not.to.have.been.called;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('discovers a route with a single method', function () {
|
|
54
|
+
serverMock.addHook
|
|
55
|
+
.withArgs('onRoute')
|
|
56
|
+
.yield({ method: 'GET', url: '/test/route', routePath: '/test/route' });
|
|
57
|
+
|
|
58
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
59
|
+
signature: "fastify.get('/test/route', [Function])",
|
|
60
|
+
url: '/test/route',
|
|
61
|
+
normalizedUrl: '/test/route',
|
|
62
|
+
method: 'get',
|
|
63
|
+
framework
|
|
64
|
+
});
|
|
65
|
+
expect(core.routeCoverage.discover).to.have.been.called;
|
|
66
|
+
expect(core.routeCoverage.discover).to.have.callCount(1);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('discovers a fully declared (.route) route with a single method', function () {
|
|
70
|
+
serverMock.route({ method: 'GET', url: '/test/route' });
|
|
71
|
+
serverMock.addHook
|
|
72
|
+
.withArgs('onRoute')
|
|
73
|
+
.yield({ method: 'GET', url: '/test/route', routePath: '/test/route' });
|
|
74
|
+
|
|
75
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
76
|
+
signature: "fastify.route({ method: 'get', url: '/test/route', handler: [Function] })",
|
|
77
|
+
url: '/test/route',
|
|
78
|
+
normalizedUrl: '/test/route',
|
|
79
|
+
method: 'get',
|
|
80
|
+
framework
|
|
81
|
+
});
|
|
82
|
+
expect(core.routeCoverage.discover).to.have.been.called;
|
|
83
|
+
expect(core.routeCoverage.discover).to.have.callCount(1);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('discovers a route with multiple methods', function () {
|
|
87
|
+
serverMock.addHook.withArgs('onRoute').yield({
|
|
88
|
+
method: ['GET', 'POST'],
|
|
89
|
+
url: '/test/route',
|
|
90
|
+
routePath: '/test/route'
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
94
|
+
signature: "fastify.get('/test/route', [Function])",
|
|
95
|
+
url: '/test/route',
|
|
96
|
+
normalizedUrl: '/test/route',
|
|
97
|
+
method: 'get',
|
|
98
|
+
framework
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
102
|
+
signature: "fastify.post('/test/route', [Function])",
|
|
103
|
+
url: '/test/route',
|
|
104
|
+
normalizedUrl: '/test/route',
|
|
105
|
+
method: 'post',
|
|
106
|
+
framework
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect(core.routeCoverage.discover).to.have.callCount(2);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('discovers a route with all methods', function () {
|
|
113
|
+
const FASTIFY_METHODS = getFastifyMethods('4.4.0');
|
|
114
|
+
serverMock.addHook.withArgs('onRoute').yield({
|
|
115
|
+
method: FASTIFY_METHODS,
|
|
116
|
+
url: '/test/route',
|
|
117
|
+
routePath: '/test/route'
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
121
|
+
signature: "fastify.all('/test/route', [Function])",
|
|
122
|
+
url: '/test/route',
|
|
123
|
+
normalizedUrl: '/test/route',
|
|
124
|
+
method: 'all',
|
|
125
|
+
framework
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('discovers a fully declared route (.route) with multiple methods', function () {
|
|
130
|
+
serverMock.route({ method: ['GET', 'POST'], url: '/test/route' });
|
|
131
|
+
|
|
132
|
+
serverMock.addHook.withArgs('onRoute').yield({
|
|
133
|
+
method: ['GET', 'POST'],
|
|
134
|
+
url: '/test/route',
|
|
135
|
+
routePath: '/test/route'
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
139
|
+
signature: "fastify.route({ method: 'get', url: '/test/route', handler: [Function] })",
|
|
140
|
+
url: '/test/route',
|
|
141
|
+
normalizedUrl: '/test/route',
|
|
142
|
+
method: 'get',
|
|
143
|
+
framework
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
147
|
+
signature: "fastify.route({ method: 'post', url: '/test/route', handler: [Function] })",
|
|
148
|
+
url: '/test/route',
|
|
149
|
+
normalizedUrl: '/test/route',
|
|
150
|
+
method: 'post',
|
|
151
|
+
framework
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
expect(core.routeCoverage.discover).to.have.callCount(2);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('signals that discovery is finished when the onReady hook is triggered', function () {
|
|
158
|
+
serverMock.addHook.withArgs('onReady').yield(sinon.stub());
|
|
159
|
+
|
|
160
|
+
expect(core.routeCoverage.discoveryFinished).to.have.been.calledOnce;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('observes a route when the route handler is called', function () {
|
|
164
|
+
const routeOptions = {
|
|
165
|
+
method: 'GET',
|
|
166
|
+
url: '/test/route',
|
|
167
|
+
routePath: '/test/route',
|
|
168
|
+
handler: sinon.stub(),
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
serverMock.addHook.withArgs('onRoute').yield(routeOptions);
|
|
172
|
+
routeOptions.handler({ raw: { method: 'GET' }, url: '/test/route' });
|
|
173
|
+
|
|
174
|
+
expect(core.routeCoverage.observe).to.have.been.calledWith({
|
|
175
|
+
signature: "fastify.get('/test/route', [Function])",
|
|
176
|
+
url: '/test/route',
|
|
177
|
+
method: 'get',
|
|
178
|
+
normalizedUrl: '/test/route',
|
|
179
|
+
framework: 'fastify'
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('removes query string from url before reporting observation', function () {
|
|
184
|
+
const routeOptions = {
|
|
185
|
+
method: 'GET',
|
|
186
|
+
url: '/test/route',
|
|
187
|
+
routePath: '/test/route',
|
|
188
|
+
handler: sinon.stub(),
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
serverMock.addHook.withArgs('onRoute').yield(routeOptions);
|
|
192
|
+
routeOptions.handler({ raw: { method: 'GET' }, url: '/test/route?input=foo' });
|
|
193
|
+
|
|
194
|
+
expect(core.routeCoverage.observe).to.have.been.calledWith({
|
|
195
|
+
signature: "fastify.get('/test/route', [Function])",
|
|
196
|
+
url: '/test/route',
|
|
197
|
+
method: 'get',
|
|
198
|
+
normalizedUrl: '/test/route',
|
|
199
|
+
framework: 'fastify'
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { expect } = require('chai');
|
|
4
|
+
const sinon = require('sinon');
|
|
5
|
+
const scopes = require('@contrast/scopes');
|
|
6
|
+
const patcher = require('@contrast/patcher');
|
|
7
|
+
const mocks = require('@contrast/test/mocks');
|
|
8
|
+
|
|
9
|
+
describe('route-coverage hapi', function () {
|
|
10
|
+
let core, route, _core, hapi, framework;
|
|
11
|
+
|
|
12
|
+
beforeEach(function () {
|
|
13
|
+
core = mocks.core();
|
|
14
|
+
core.logger = mocks.logger();
|
|
15
|
+
core.routeCoverage = mocks.routeCoverage();
|
|
16
|
+
core.scopes = scopes(core);
|
|
17
|
+
core.depHooks = mocks.depHooks();
|
|
18
|
+
core.patcher = patcher(core);
|
|
19
|
+
|
|
20
|
+
route = {
|
|
21
|
+
settings: {
|
|
22
|
+
handler: sinon.stub()
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
_core = {
|
|
27
|
+
router: {
|
|
28
|
+
add() {
|
|
29
|
+
return { route };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
hapi = {
|
|
35
|
+
server() {
|
|
36
|
+
return { _core };
|
|
37
|
+
},
|
|
38
|
+
Server() {
|
|
39
|
+
return { _core };
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
framework = 'hapi';
|
|
43
|
+
|
|
44
|
+
core.depHooks.resolve.withArgs({ name: '@hapi/hapi', version: '>=18 <22' }).yields(hapi);
|
|
45
|
+
require('./hapi')(core).install();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
['server', 'Server'].forEach((server) => {
|
|
50
|
+
|
|
51
|
+
it('does not report a non-existent route', function () {
|
|
52
|
+
const hapiServer = hapi[server]();
|
|
53
|
+
hapiServer._core.router.add();
|
|
54
|
+
expect(core.routeCoverage.discover).not.to.have.been.called;
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('does not report a route missing a method', function () {
|
|
58
|
+
const hapiServer = hapi[server]();
|
|
59
|
+
hapiServer._core.router.add({ path: '/foo' });
|
|
60
|
+
expect(core.routeCoverage.discover).not.to.have.been.called;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('does not report a route missing a path', function () {
|
|
64
|
+
const hapiServer = hapi[server]();
|
|
65
|
+
hapiServer._core.router.add({ method: 'GET' });
|
|
66
|
+
expect(core.routeCoverage.discover).not.to.have.been.called;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('discovers a route with a single method', function () {
|
|
70
|
+
const hapiServer = hapi[server]();
|
|
71
|
+
hapiServer._core.router.add({ method: 'GET', path: '/foo' });
|
|
72
|
+
|
|
73
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
74
|
+
signature: "server.route({ method: 'get', path: '/foo', handler: [Function] })",
|
|
75
|
+
url: '/foo',
|
|
76
|
+
normalizedUrl: '/foo',
|
|
77
|
+
method: 'get',
|
|
78
|
+
framework
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('discovers a route with multiple methods', function () {
|
|
83
|
+
const hapiServer = hapi[server]();
|
|
84
|
+
hapiServer._core.router.add({ method: ['GET', 'PUT'], path: '/foo' });
|
|
85
|
+
|
|
86
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
87
|
+
signature: "server.route({ method: 'get', path: '/foo', handler: [Function] })",
|
|
88
|
+
url: '/foo',
|
|
89
|
+
normalizedUrl: '/foo',
|
|
90
|
+
method: 'get',
|
|
91
|
+
framework
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
95
|
+
signature: "server.route({ method: 'put', path: '/foo', handler: [Function] })",
|
|
96
|
+
url: '/foo',
|
|
97
|
+
normalizedUrl: '/foo',
|
|
98
|
+
method: 'put',
|
|
99
|
+
framework
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('observes a route when a route handler is exercised', async function () {
|
|
104
|
+
const hapiServer = hapi[server]();
|
|
105
|
+
const { route } = hapiServer._core.router.add({ method: 'get', path: '/foo' });
|
|
106
|
+
route.settings.handler({ method: 'get', path: '/foo', route: { path: '/foo' } });
|
|
107
|
+
expect(core.routeCoverage.observe).to.have.been.calledWith({
|
|
108
|
+
url: '/foo',
|
|
109
|
+
normalizedUrl: '/foo',
|
|
110
|
+
method: 'get'
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('observes a route when a route contains parameters', async function () {
|
|
115
|
+
const hapiServer = hapi[server]();
|
|
116
|
+
const { route } = hapiServer._core.router.add({ method: 'get', path: '/foo/{id}' });
|
|
117
|
+
route.settings.handler({ method: 'get', path: '/foo/1', route: { path: '/foo/{id}' } });
|
|
118
|
+
expect(core.routeCoverage.observe).to.have.been.calledWith({
|
|
119
|
+
url: '/foo/1',
|
|
120
|
+
normalizedUrl: '/foo/{id}',
|
|
121
|
+
method: 'get'
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { expect } = require('chai');
|
|
4
|
+
const sinon = require('sinon');
|
|
5
|
+
const scopes = require('@contrast/scopes');
|
|
6
|
+
const patcher = require('@contrast/patcher');
|
|
7
|
+
const mocks = require('@contrast/test/mocks');
|
|
8
|
+
const { METHODS } = require('../utils/methods');
|
|
9
|
+
|
|
10
|
+
describe('route-coverage koa', function () {
|
|
11
|
+
let core, Router, framework, Koa, app, handlerFn;
|
|
12
|
+
|
|
13
|
+
beforeEach(function () {
|
|
14
|
+
core = mocks.core();
|
|
15
|
+
core.logger = mocks.logger();
|
|
16
|
+
core.routeCoverage = mocks.routeCoverage();
|
|
17
|
+
core.scopes = scopes(core);
|
|
18
|
+
core.depHooks = mocks.depHooks();
|
|
19
|
+
core.patcher = patcher(core);
|
|
20
|
+
|
|
21
|
+
framework = 'koa';
|
|
22
|
+
Router = function Router() { };
|
|
23
|
+
Router.router = {
|
|
24
|
+
stack: []
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
handlerFn = sinon.stub();
|
|
28
|
+
Router.prototype.use = (path, fn) => Router.router.stack.push({ methods: [], path, stack: [fn] });
|
|
29
|
+
Router.prototype.get = (path, fn) => Router.router.stack.push({ methods: ['GET'], path, stack: [fn] });
|
|
30
|
+
Router.prototype.all = (path, fn) => Router.router.stack.push({ methods: METHODS, path, stack: [fn] });
|
|
31
|
+
Router.prototype.routes = () => Router;
|
|
32
|
+
|
|
33
|
+
Koa = function() {};
|
|
34
|
+
Koa.prototype.use = sinon.stub();
|
|
35
|
+
core.depHooks.resolve.withArgs({ name: 'koa', version: '>=2.3.0' }).yields(Koa);
|
|
36
|
+
require('./koa')(core).install();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('does not report when called with nothing', function () {
|
|
40
|
+
app = new Koa();
|
|
41
|
+
app.use();
|
|
42
|
+
expect(core.routeCoverage.discover).not.to.have.been.called;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('does not report when called with non-router', function () {
|
|
46
|
+
app = new Koa();
|
|
47
|
+
app.use({ not: 'a router' });
|
|
48
|
+
expect(core.routeCoverage.discover).not.to.have.been.called;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('does not report unmounted routes', function () {
|
|
52
|
+
const router = new Router();
|
|
53
|
+
router.get('/test/route', handlerFn);
|
|
54
|
+
|
|
55
|
+
expect(core.routeCoverage.discover).not.to.have.been.called;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('does not report a non-existent route', function () {
|
|
59
|
+
const router = new Router();
|
|
60
|
+
router.get();
|
|
61
|
+
|
|
62
|
+
app = new Koa();
|
|
63
|
+
app.use(router.routes());
|
|
64
|
+
expect(core.routeCoverage.discover).not.to.have.been.called;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('does not report when path is an array', function () {
|
|
68
|
+
const router = new Router();
|
|
69
|
+
router.get(['/test/route'], handlerFn);
|
|
70
|
+
|
|
71
|
+
app = new Koa();
|
|
72
|
+
app.use(router.routes());
|
|
73
|
+
expect(core.routeCoverage.discover).not.to.have.been.called;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('discovers a route', function () {
|
|
77
|
+
const router = new Router();
|
|
78
|
+
router.get('/test/route', handlerFn);
|
|
79
|
+
|
|
80
|
+
app = new Koa();
|
|
81
|
+
app.use(router.routes());
|
|
82
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
83
|
+
signature: "Router.get('/test/route', [Function])",
|
|
84
|
+
url: '/test/route',
|
|
85
|
+
normalizedUrl: '/test/route',
|
|
86
|
+
method: 'get',
|
|
87
|
+
framework
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('discovers a .use route', function () {
|
|
92
|
+
const router = new Router();
|
|
93
|
+
router.use('/test/route', handlerFn);
|
|
94
|
+
|
|
95
|
+
app = new Koa();
|
|
96
|
+
app.use(router.routes());
|
|
97
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
98
|
+
signature: "Router.use('/test/route', [Function])",
|
|
99
|
+
url: '/test/route',
|
|
100
|
+
normalizedUrl: '/test/route',
|
|
101
|
+
method: 'use',
|
|
102
|
+
framework
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('discovers a .all route', function () {
|
|
107
|
+
const router = new Router();
|
|
108
|
+
router.all('/test/route', handlerFn);
|
|
109
|
+
|
|
110
|
+
app = new Koa();
|
|
111
|
+
app.use(router.routes());
|
|
112
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
113
|
+
signature: "Router.all('/test/route', [Function])",
|
|
114
|
+
url: '/test/route',
|
|
115
|
+
normalizedUrl: '/test/route',
|
|
116
|
+
method: 'all',
|
|
117
|
+
framework
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('does not observe a route without a valid request', async function () {
|
|
122
|
+
const router = new Router();
|
|
123
|
+
router.get('/test/route', handlerFn);
|
|
124
|
+
|
|
125
|
+
app = new Koa();
|
|
126
|
+
app.use(router.routes());
|
|
127
|
+
|
|
128
|
+
Router.router.stack[0].stack[0]({});
|
|
129
|
+
expect(core.routeCoverage.observe).to.not.have.been.called;
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('observes a route when a route handler is exercised', async function () {
|
|
133
|
+
const router = new Router();
|
|
134
|
+
router.get('/test/route', handlerFn);
|
|
135
|
+
|
|
136
|
+
app = new Koa();
|
|
137
|
+
app.use(router.routes());
|
|
138
|
+
|
|
139
|
+
const request = {
|
|
140
|
+
url: '/test/route',
|
|
141
|
+
method: 'get'
|
|
142
|
+
};
|
|
143
|
+
Router.router.stack[0].stack[0]({ request });
|
|
144
|
+
expect(core.routeCoverage.observe).to.have.been.calledWith({
|
|
145
|
+
signature: "Router.get('/test/route', [Function])",
|
|
146
|
+
url: '/test/route',
|
|
147
|
+
normalizedUrl: '/test/route',
|
|
148
|
+
method: 'get',
|
|
149
|
+
framework: 'koa'
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { expect } = require('chai');
|
|
4
|
+
const sinon = require('sinon');
|
|
5
|
+
const scopes = require('@contrast/scopes');
|
|
6
|
+
const patcher = require('@contrast/patcher');
|
|
7
|
+
const mocks = require('@contrast/test/mocks');
|
|
8
|
+
|
|
9
|
+
describe('route-coverage restify', function () {
|
|
10
|
+
let core, restify, serverMock, handler, framework;
|
|
11
|
+
|
|
12
|
+
beforeEach(function () {
|
|
13
|
+
core = mocks.core();
|
|
14
|
+
core.logger = mocks.logger();
|
|
15
|
+
core.routeCoverage = mocks.routeCoverage();
|
|
16
|
+
core.scopes = scopes(core);
|
|
17
|
+
core.depHooks = mocks.depHooks();
|
|
18
|
+
core.patcher = patcher(core);
|
|
19
|
+
|
|
20
|
+
handler = sinon.stub();
|
|
21
|
+
serverMock = {
|
|
22
|
+
router: {
|
|
23
|
+
mount (path, method) {
|
|
24
|
+
return {
|
|
25
|
+
path,
|
|
26
|
+
method,
|
|
27
|
+
chain: {
|
|
28
|
+
_stack: [
|
|
29
|
+
handler
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
framework = 'restify';
|
|
38
|
+
restify = {
|
|
39
|
+
createServer: () => serverMock
|
|
40
|
+
};
|
|
41
|
+
core.depHooks.resolve.withArgs({ name: 'restify', version: '>= 8.0.0' }).yields(restify);
|
|
42
|
+
require('./restify')(core).install();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('does not report a non-existent route', function () {
|
|
46
|
+
const { router } = restify.createServer();
|
|
47
|
+
router.mount();
|
|
48
|
+
expect(core.routeCoverage.discover).not.to.have.been.called;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('does not report an incomplete route', function () {
|
|
52
|
+
const { router } = restify.createServer();
|
|
53
|
+
router.mount('/foo');
|
|
54
|
+
expect(core.routeCoverage.discover).not.to.have.been.called;
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('does not report an invalid route', function () {
|
|
58
|
+
const { router } = restify.createServer();
|
|
59
|
+
router.mount(42, 'GET');
|
|
60
|
+
expect(core.routeCoverage.discover).not.to.have.been.called;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('discovers a route', function () {
|
|
64
|
+
const { router } = restify.createServer();
|
|
65
|
+
router.mount('/foo', 'GET');
|
|
66
|
+
|
|
67
|
+
expect(core.routeCoverage.discover).to.have.been.calledWith({
|
|
68
|
+
signature: "server.get('/foo', [Function])",
|
|
69
|
+
url: '/foo',
|
|
70
|
+
normalizedUrl: '/foo',
|
|
71
|
+
method: 'get',
|
|
72
|
+
framework
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('observes a route', async function () {
|
|
77
|
+
const { router } = restify.createServer();
|
|
78
|
+
const route = router.mount('/foo', 'GET');
|
|
79
|
+
const handler = route.chain._stack[0];
|
|
80
|
+
handler({
|
|
81
|
+
url: '/foo?input=bar',
|
|
82
|
+
method: 'GET'
|
|
83
|
+
});
|
|
84
|
+
expect(core.routeCoverage.observe).to.have.been.calledWith({
|
|
85
|
+
signature: "server.get('/foo', [Function])",
|
|
86
|
+
url: '/foo',
|
|
87
|
+
normalizedUrl: '/foo',
|
|
88
|
+
method: 'get',
|
|
89
|
+
framework
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/route-coverage",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.0",
|
|
4
4
|
"description": "Handles route discovery and observation",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"test": "../scripts/test.sh"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@contrast/common": "1.
|
|
20
|
+
"@contrast/common": "1.23.0",
|
|
21
21
|
"@contrast/fn-inspect": "^4.0.0"
|
|
22
22
|
}
|
|
23
23
|
}
|