@contrast/agentify 1.42.1 → 1.42.3

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.
@@ -31,8 +31,6 @@ module.exports = function init(core) {
31
31
 
32
32
  core.rewriteHooks = {
33
33
  install() {
34
- if (!core.config.agent.node.rewrite.enable) return;
35
-
36
34
  // don't define this prior to install(), since it will interfere with other
37
35
  // components that also need to instrument it e.g. dep-hooks.
38
36
  _compile = Module.prototype._compile;
@@ -44,6 +42,10 @@ module.exports = function init(core) {
44
42
  * @param {string} filename The file path of the module
45
43
  */
46
44
  Module.prototype._compile = function (content, filename) {
45
+ if (!core.config.agent.node.rewrite.enable) {
46
+ return Reflect.apply(_compile, this, [content, filename]);
47
+ }
48
+
47
49
  /** @type {import('@contrast/rewriter').RewriteOpts} */
48
50
  const options = {
49
51
  filename,
@@ -57,9 +59,9 @@ module.exports = function init(core) {
57
59
  return Reflect.apply(_compile, this, [content, filename]);
58
60
  }
59
61
 
60
- const result = core.rewriter.rewriteSync(content, options);
61
62
 
62
63
  try {
64
+ const result = core.rewriter.rewriteSync(content, options);
63
65
  const compiled = Reflect.apply(_compile, this, [result.code, filename]);
64
66
 
65
67
  if (core.config.agent.node.rewrite.cache.enable) {
@@ -70,7 +72,7 @@ module.exports = function init(core) {
70
72
  } catch (err) {
71
73
  core.logger.warn(
72
74
  { err },
73
- 'Failed to compile rewritten code for %s, compiling original code.',
75
+ 'Failed to rewrite code for %s, continuing with original code.',
74
76
  filename,
75
77
  );
76
78
 
package/lib/utils.js CHANGED
@@ -15,6 +15,7 @@
15
15
 
16
16
  'use strict';
17
17
 
18
+ const { platform, arch } = require('os');
18
19
  const path = require('path');
19
20
  const process = require('process');
20
21
  const semver = require('semver');
@@ -36,6 +37,7 @@ function preStartupValidation(core) {
36
37
  assertNoExperimentalFeatureFlags();
37
38
  assertSupportedNodeVersion(core.nodeEngines || nodeEngines);
38
39
  assertSupportedPreloadUsage();
40
+ assertEnvironmentMatchesBuild();
39
41
  }
40
42
 
41
43
  /**
@@ -57,6 +59,25 @@ function assertSupportedNodeVersion(engines) {
57
59
  }
58
60
  }
59
61
 
62
+ /**
63
+ * Checks that some of our precompiled dependencies are included and callable.
64
+ * This should ensure that we're running in the same environment that we were
65
+ * installed in.
66
+ * @throws {Error}
67
+ */
68
+ function assertEnvironmentMatchesBuild() {
69
+ try {
70
+ /* eslint-disable node/no-extraneous-require */
71
+ // since swc is a codependency of the agent it should always be present but
72
+ // doesn't necessarily need to be included in this module's dependencies.
73
+ require('@swc/core').parseSync('');
74
+ } catch (cause) {
75
+ throw (cause.message === 'Bindings not found.') ?
76
+ new Error(`Contrast cannot detect the correct precompiled dependencies for the current environment: ${platform()}-${arch()}. This typically occurs when deploying an installation from one environment to a different execution environment.`, { cause })
77
+ : cause;
78
+ }
79
+ }
80
+
60
81
  /**
61
82
  * Checks that no experimental feature flags are used.
62
83
  * @throws {Error}
@@ -169,5 +190,6 @@ module.exports = {
169
190
  assertSupportedNodeVersion,
170
191
  assertSupportedPreloadUsage,
171
192
  assertNoExperimentalFeatureFlags,
193
+ assertEnvironmentMatchesBuild,
172
194
  preStartupValidation,
173
195
  };
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "@contrast/agentify",
3
- "version": "1.42.1",
3
+ "version": "1.42.3",
4
4
  "description": "Configures Contrast agent services and instrumentation within an application",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
7
7
  "files": [
8
- "lib/"
8
+ "lib/",
9
+ "!*.test.*",
10
+ "!tsconfig.*",
11
+ "!*.map"
9
12
  ],
10
13
  "main": "lib/index.js",
11
14
  "types": "lib/index.d.ts",
@@ -18,20 +21,20 @@
18
21
  },
19
22
  "dependencies": {
20
23
  "@contrast/common": "1.29.1",
21
- "@contrast/config": "1.40.1",
22
- "@contrast/core": "1.45.1",
23
- "@contrast/deadzones": "1.17.1",
24
- "@contrast/dep-hooks": "1.14.1",
25
- "@contrast/esm-hooks": "2.19.1",
24
+ "@contrast/config": "1.40.2",
25
+ "@contrast/core": "1.45.2",
26
+ "@contrast/deadzones": "1.17.2",
27
+ "@contrast/dep-hooks": "1.14.2",
28
+ "@contrast/esm-hooks": "2.19.3",
26
29
  "@contrast/find-package-json": "^1.1.0",
27
- "@contrast/instrumentation": "1.24.1",
28
- "@contrast/logger": "1.18.1",
29
- "@contrast/metrics": "1.22.1",
30
- "@contrast/patcher": "1.17.1",
30
+ "@contrast/instrumentation": "1.24.2",
31
+ "@contrast/logger": "1.18.2",
32
+ "@contrast/metrics": "1.22.2",
33
+ "@contrast/patcher": "1.17.2",
31
34
  "@contrast/perf": "1.3.1",
32
- "@contrast/reporter": "1.41.1",
33
- "@contrast/rewriter": "1.21.1",
34
- "@contrast/scopes": "1.15.1",
35
+ "@contrast/reporter": "1.41.2",
36
+ "@contrast/rewriter": "1.21.3",
37
+ "@contrast/scopes": "1.15.2",
35
38
  "on-finished": "^2.4.1",
36
39
  "semver": "^7.6.0"
37
40
  }
@@ -1,174 +0,0 @@
1
- 'use strict';
2
-
3
- const { expect } = require('chai');
4
- const mocks = require('@contrast/test/mocks');
5
- const functionHooks = require('./function-hooks');
6
-
7
- describe('agentify function-hooks', function () {
8
- let core;
9
-
10
- beforeEach(function () {
11
- core = mocks.core();
12
- core.patcher = mocks.patcher();
13
- core.logger = mocks.logger();
14
- core.depHooks = mocks.depHooks();
15
- core.scopes = mocks.scopes();
16
- core.rewriter = mocks.rewriter();
17
- });
18
-
19
- it('should install function hooks', function () {
20
- functionHooks(core);
21
- core.functionHooks.install();
22
- expect(core.functionHooks.installed).to.be.true;
23
- });
24
-
25
- it('should skip if it is already installed', function () {
26
- functionHooks(core);
27
- core.functionHooks.installed = true;
28
- core.functionHooks.install();
29
-
30
- expect(core.logger.debug).not.to.have.been.called;
31
- });
32
-
33
- it('should return cached value when the function is already called', function () {
34
- const functionA = function sum(a, b) {
35
- eval(global.ContrastMethods.eval('2 + 2'));
36
- return a + b;
37
- };
38
-
39
- const cachedValue = 'cachedValue';
40
-
41
- functionHooks(core);
42
-
43
- core.functionHooks.cache.set(functionA, cachedValue);
44
-
45
- const data = {
46
- obj: functionA,
47
- };
48
-
49
- let result;
50
- core.patcher.patch.callsFake((prototype, method, options) => {
51
- result = options.around(() => ({}), data);
52
- });
53
-
54
- core.functionHooks.install();
55
-
56
- expect(result).to.equal(cachedValue);
57
- });
58
-
59
- it('should call the function itself when the function is not hooked', function () {
60
- const functionA = function sum(a, b) {
61
- eval(global.ContrastMethods.eval('2 + 2'));
62
- return a + b;
63
- };
64
-
65
- functionHooks(core);
66
-
67
- const data = {
68
- obj: functionA,
69
- };
70
-
71
- let result;
72
- core.patcher.patch.callsFake((prototype, method, options) => {
73
- result = options.around(functionA.toString.bind(functionA), data);
74
- });
75
-
76
- const unwriteResult = `function sum(a, b) {
77
- eval(2 + 2);
78
- return a + b;
79
- }`;
80
-
81
- core.rewriter.unwriteSync.callsFake((data) => unwriteResult);
82
-
83
- core.functionHooks.install();
84
-
85
- expect(result).to.equal(unwriteResult);
86
- expect(core.functionHooks.cache.get(data.obj)).to.equal(unwriteResult);
87
- });
88
-
89
- it('should the call the original function of the hooked function', function () {
90
- const functionA = function sum(a, b) {
91
- eval(global.ContrastMethods.eval('2 + 2'));
92
- return a + b;
93
- };
94
-
95
- const functionB = function sum(a, b) {
96
- eval(global.ContrastMethods.eval('2 + 2'));
97
- return a + b + 2;
98
- };
99
-
100
- functionHooks(core);
101
-
102
- const weekMap = core.patcher.hookedFunctions;
103
- weekMap.set(functionA, { fn: functionB });
104
-
105
- const data = {
106
- obj: functionA,
107
- };
108
-
109
- let result;
110
- core.patcher.patch.callsFake((prototype, method, options) => {
111
- result = options.around(functionA.toString.bind(functionA), data);
112
- });
113
-
114
- const unwriteResult = `function sum(a, b) {
115
- eval(2 + 2);
116
- return a + b + 2;
117
- }`;
118
-
119
- core.rewriter.unwriteSync.callsFake((data) => unwriteResult);
120
-
121
-
122
- core.functionHooks.install();
123
-
124
- expect(result).to.equal(unwriteResult);
125
- expect(core.functionHooks.cache.get(data.obj)).to.equal(unwriteResult);
126
- });
127
-
128
- it('should log warning message when there is a problem unwriting the code', function () {
129
- const err = new Error('Error');
130
- const functionA = function sum(a, b) {
131
- eval(global.ContrastMethods.eval('2 + 2'));
132
- return a + b;
133
- };
134
-
135
- functionHooks(core);
136
-
137
- const data = {
138
- obj: functionA,
139
- };
140
-
141
- core.patcher.patch.callsFake((prototype, method, options) => {
142
- options.around(functionA.toString.bind(functionA), data);
143
- });
144
-
145
- core.rewriter.unwriteSync.throws(err);
146
-
147
- core.functionHooks.install();
148
-
149
- expect(core.logger.warn).to.have.been.calledWith({ err, code: functionA.toString() }, 'Failed to unwrite function code');
150
- });
151
-
152
- it('should call the original toString if the function is not hooked', function () {
153
- const functionA = () => 1;
154
- functionHooks(core);
155
- core.functionHooks.install();
156
-
157
- const resultOfA = functionA.toString();
158
-
159
- expect(resultOfA).to.equal('() => 1');
160
- });
161
-
162
- it('should uninstall the hooks', function () {
163
- const { toString } = Function.prototype;
164
- const functionA = () => 1;
165
- functionHooks(core);
166
- core.functionHooks.install();
167
- core.functionHooks.uninstall();
168
-
169
- const resultOfA = functionA.toString();
170
-
171
- expect(resultOfA).to.equal('() => 1');
172
- expect(Function.prototype.toString).to.equal(toString);
173
- });
174
- });
@@ -1,92 +0,0 @@
1
- 'use strict';
2
-
3
- const { expect } = require('chai');
4
- const path = require('path');
5
- const proxyquire = require('proxyquire');
6
- const sinon = require('sinon');
7
- const { ConfigSource: { ENVIRONMENT_VARIABLE } } = require('@contrast/config');
8
- const mocks = require('@contrast/test/mocks');
9
- const { delay } = require('@contrast/test/utils');
10
-
11
-
12
- describe('heap dump', function () {
13
- let core,
14
- heapSnapshots,
15
- fsMock,
16
- v8Mock,
17
- dirname,
18
- delayMs,
19
- windowMs,
20
- snapshotPath;
21
-
22
- beforeEach(function () {
23
- fsMock = {
24
- promises: {
25
- mkdir: sinon.stub().resolves(''),
26
- }
27
- };
28
- v8Mock = {
29
- writeHeapSnapshot: sinon.stub(),
30
- };
31
- delayMs = windowMs = 100;
32
- snapshotPath = 'contrast_heap_dumps';
33
- dirname = path.resolve(process.cwd(), snapshotPath);
34
-
35
- core = {};
36
- core.config = mocks.config();
37
- core.logger = mocks.logger();
38
-
39
- core.config.setValue('agent.heap_dump.enable', true, ENVIRONMENT_VARIABLE);
40
- core.config.setValue('agent.heap_dump.path', snapshotPath, ENVIRONMENT_VARIABLE);
41
- core.config.setValue('agent.heap_dump.delay_ms', delayMs, ENVIRONMENT_VARIABLE);
42
- core.config.setValue('agent.heap_dump.window_ms', windowMs, ENVIRONMENT_VARIABLE);
43
- core.config.setValue('agent.heap_dump.count', 2, ENVIRONMENT_VARIABLE);
44
-
45
- const init = proxyquire('./heap-snapshots', {
46
- fs: fsMock,
47
- v8: v8Mock,
48
- });
49
-
50
- heapSnapshots = init(core);
51
- });
52
-
53
- describe('.writeHeapSnapshot()', function () {
54
- it('writes snapshots', function () {
55
- heapSnapshots.writeHeapSnapshot();
56
- expect(core.logger.info).to.have.been.calledWithMatch(
57
- 'Writing heap snapshot at %s',
58
- dirname,
59
- );
60
- expect(v8Mock.writeHeapSnapshot).to.have.been.calledWithMatch(dirname);
61
- });
62
- });
63
-
64
- describe('.install()', function () {
65
- it('logs when the directory cannot be created', async function () {
66
- const err = new Error('ENOENT');
67
- fsMock.promises.mkdir.rejects(err);
68
- await heapSnapshots.install();
69
- expect(core.logger.error).to.have.been.calledOnceWithExactly({
70
- err,
71
- }, 'Unable to create snapshot directory. No heap snapshots will be taken.');
72
- expect(v8Mock.writeHeapSnapshot).not.to.have.been.called;
73
- });
74
-
75
-
76
- it('does not write snapshots if enable is false', function () {
77
- core.config.setValue('agent.heap_dumps.enable', false, ENVIRONMENT_VARIABLE);
78
- heapSnapshots.install();
79
- expect(v8Mock.writeHeapSnapshot).not.to.have.been.called;
80
- });
81
-
82
- it('writes snapshots at the correct intervals', async function () {
83
- heapSnapshots.install();
84
- expect(v8Mock.writeHeapSnapshot).not.to.have.been.called;
85
- const pad = 50;
86
- await delay(delayMs + windowMs + pad);
87
- expect(v8Mock.writeHeapSnapshot).to.have.callCount(1);
88
- await delay(windowMs);
89
- expect(v8Mock.writeHeapSnapshot).to.have.callCount(2);
90
- });
91
- });
92
- });
package/lib/index.test.js DELETED
@@ -1,188 +0,0 @@
1
- 'use strict';
2
-
3
- const { expect } = require('chai');
4
- const proxyquire = require('proxyquire');
5
- const sinon = require('sinon');
6
- const mocks = require('@contrast/test/mocks');
7
- const Perf = require('@contrast/perf');
8
-
9
- describe('agentify', function () {
10
- const Module = require('module');
11
- let agentify, preRunMain, runMain;
12
- const allAgentify = new Map();
13
- // the sinon stubs are captured in beforeEach(), before the agentify call,
14
- // because the Perf wrapper will replace them on the core object. this way we
15
- // can still determine that the function was called/not called as expected.
16
- let loggerError;
17
- let depHooksInstall;
18
-
19
- beforeEach(function () {
20
- // added because the config validation code verifies that the agent has
21
- // the three required settings and throws if not.
22
- process.env.CONTRAST__API__API_KEY = 'testing';
23
- process.env.CONTRAST__API__SERVICE_KEY = 'testing';
24
- process.env.CONTRAST__API__USER_NAME = 'testing';
25
-
26
- agentify = proxyquire('.', {
27
- '@contrast/logger': {
28
- default(core) {
29
- core.logger = mocks.logger();
30
- loggerError = core.logger.error;
31
- return core.logger;
32
- }
33
- },
34
- '@contrast/dep-hooks'(core) {
35
- core.depHooks = mocks.depHooks();
36
- depHooksInstall = core.depHooks.install;
37
- return core.depHooks;
38
- },
39
- })();
40
- preRunMain = sinon.stub();
41
- runMain = sinon.stub(Module, 'runMain');
42
- });
43
-
44
- afterEach(function() {
45
- Perf.fromAllToMap('agentify', allAgentify);
46
- });
47
-
48
- after(function() {
49
- delete process.env.CONTRAST__API__API_KEY;
50
- delete process.env.CONTRAST__API__SERVICE_KEY;
51
- delete process.env.CONTRAST__API_USER_NAME;
52
- const stats = Perf.getStats(allAgentify);
53
- for (const [key, { n, totalMicros, mean }] of stats.entries()) {
54
- console.log(key, n, totalMicros, 'nsec', mean, 'mean');
55
- }
56
- });
57
-
58
- it('invoking with callback will initialize and patch runMain', async function () {
59
- const core = await agentify(preRunMain, {
60
- noValidate: true,
61
- installOrder: ['depHooks'],
62
- });
63
-
64
- await Module.runMain();
65
-
66
- expect(preRunMain).to.have.been.calledWith(core);
67
- expect(runMain).to.have.been.called;
68
- expect(depHooksInstall).to.have.been.called;
69
- expect(loggerError).not.to.have.been.called;
70
- });
71
-
72
- it('will not run install methods when installOrder option is empty array', async function () {
73
- const _core = await agentify(preRunMain, { installOrder: [] });
74
- await Module.runMain();
75
-
76
- expect(depHooksInstall).not.called;
77
- expect(loggerError).not.called;
78
- });
79
-
80
-
81
- it('handles errors in plugin initialization functions with logger', async function () {
82
- const err = new Error('oof');
83
- const agentify = proxyquire('.', {
84
- '@contrast/logger': {
85
- default(core) {
86
- return core.logger = mocks.logger();
87
- }
88
- },
89
- '@contrast/dep-hooks'() {
90
- throw err;
91
- },
92
- })();
93
-
94
- const core = await agentify(preRunMain, {
95
- noValidate: true,
96
- installOrder: ['depHooks'],
97
- });
98
-
99
- await Module.runMain();
100
-
101
- expect(core).not.to.have.property('depHooks');
102
- expect(runMain).to.have.been.called;
103
- expect(core.logger.error).to.have.been.calledWith({ err });
104
- });
105
-
106
-
107
- it('handles errors in plugin initialization functions without logger', async function () {
108
- const consoleErrorStub = sinon.stub(console, 'error');
109
- const err = new Error('boop');
110
- const agentify = proxyquire('.', {
111
- '@contrast/logger': {
112
- default() {
113
- throw err;
114
- }
115
- },
116
- '@contrast/dep-hooks'(core) {
117
- return core.depHooks = mocks.depHooks();
118
- },
119
- })();
120
-
121
- const core = await agentify(preRunMain, {
122
- noValidate: true,
123
- installOrder: ['depHooks'],
124
- });
125
-
126
- await Module.runMain();
127
-
128
- expect(core).not.to.have.property('depHooks');
129
- expect(runMain).to.have.been.called;
130
- expect(consoleErrorStub).to.have.been.called;
131
- });
132
-
133
- it('handles errors in the preRunMain hook', async function () {
134
- const err = new Error('bonk');
135
- preRunMain.throws(err);
136
- const core = await agentify(preRunMain, {
137
- noValidate: true,
138
- installOrder: ['depHooks'],
139
- });
140
-
141
- await Module.runMain();
142
-
143
- expect(depHooksInstall).not.to.have.been.called;
144
- expect(runMain).to.have.been.called;
145
- expect(core.logger.error).to.have.been.calledWith({ err });
146
- });
147
-
148
- it('handles "unregistered" services', async function () {
149
- const core = await agentify(preRunMain, {
150
- noValidate: true,
151
- installOrder: ['foo'],
152
- });
153
-
154
- // Having non-existent service in list is handled during initialization
155
- await Module.runMain();
156
-
157
- expect(core.logger.error).not.to.have.been.called;
158
- });
159
-
160
- it('handles the return value of preRunMain', async function () {
161
- const install = sinon.stub();
162
- preRunMain.returns({ install });
163
- const core = await agentify(preRunMain, { installOrder: [] });
164
-
165
- // Having non-existent service in list is handled during initialization
166
- await Module.runMain();
167
-
168
- expect(install).to.have.been.called;
169
- expect(core.logger.error).not.to.have.been.called;
170
- });
171
-
172
- it('handles the return value of preRunMain when erroring', async function () {
173
- const err = new Error('err in install');
174
- const install = sinon.stub().throws(err);
175
- preRunMain.returns({ install });
176
- const core = await agentify(preRunMain, { installOrder: [] });
177
-
178
- // Having non-existent service in list is handled during initialization
179
- await Module.runMain();
180
-
181
- expect(install).to.have.been.called;
182
- expect(core.logger.error).to.have.been.calledWith({ err });
183
- });
184
-
185
- it('requires init function arg', async function () {
186
- return expect(agentify('not a function')).to.be.rejectedWith('Invalid usage: first argument must be a function');
187
- });
188
- });
@@ -1,139 +0,0 @@
1
- // @ts-check
2
- 'use strict';
3
-
4
- const { expect } = require('chai');
5
- const { readFileSync } = require('fs');
6
- const Module = require('module');
7
- const sinon = require('sinon');
8
- const mocks = require('@contrast/test/mocks');
9
-
10
- const testFilePath = require.resolve('../test/resources/file');
11
- const deadzonedFilePath = require.resolve('../test/node_modules/bcryptjs/dist/bcrypt.js');
12
- const testFileContent = readFileSync(testFilePath).toString();
13
-
14
- describe('agentify rewrite-hooks', function () {
15
- let core, complileSpy, rewriteHooks;
16
-
17
- beforeEach(function () {
18
- core = mocks.core();
19
- core.logger = mocks.logger();
20
- core.config = mocks.config();
21
- core.rewriter = mocks.rewriter();
22
-
23
- complileSpy = sinon.spy(Module.prototype, '_compile');
24
-
25
- rewriteHooks = require('./rewrite-hooks')(core);
26
- });
27
-
28
- afterEach(function () {
29
- delete require.cache[testFilePath];
30
- rewriteHooks.uninstall();
31
- });
32
-
33
- it('rewrites code when the cache returns no results expected', function () {
34
- core.rewriter.cache.readSync.returns(undefined);
35
- rewriteHooks.install();
36
-
37
- require(testFilePath);
38
- expect(core.rewriter.rewriteSync).to.have.been.calledWith(testFileContent, {
39
- filename: testFilePath,
40
- isModule: false,
41
- inject: true,
42
- wrap: true,
43
- });
44
- expect(core.logger.warn).not.to.have.been.called;
45
- expect(complileSpy).to.have.been.calledWith(
46
- core.rewriter.rewriteSync.getCall(0).returnValue.code,
47
- testFilePath,
48
- );
49
- });
50
-
51
- it('does not not rewrite code when rewrite.enable is false', function () {
52
- core.config.agent.node.rewrite.enable = false;
53
- rewriteHooks.install();
54
-
55
- require(testFilePath);
56
- expect(core.rewriter.rewriteSync).not.to.have.been.called;
57
- });
58
-
59
- it('does not not rewrite code when the cache returns results', function () {
60
- rewriteHooks.install();
61
-
62
- require(testFilePath);
63
- expect(core.rewriter.rewriteSync).not.to.have.been.called;
64
- expect(complileSpy).to.have.been.calledWith(
65
- core.rewriter.cache.readSync.getCall(0).returnValue,
66
- testFilePath,
67
- );
68
- });
69
-
70
- it('logs a debug message when a file is rewrite-deadzoned', function() {
71
- rewriteHooks.install();
72
-
73
- require(deadzonedFilePath);
74
-
75
- expect(core.logger.debug).calledWith(
76
- 'Skipping rewrite-deadzoned file %s',
77
- deadzonedFilePath
78
- );
79
- });
80
-
81
- it('logs a warning if the rewritten module does not compile', function () {
82
- core.rewriter.cache.readSync.returns(undefined);
83
- core.rewriter.rewriteSync.callsFake((content) => ({
84
- code: content.replace('const variable', 'const variable const a'),
85
- }));
86
-
87
- rewriteHooks.install();
88
-
89
- require(testFilePath);
90
- expect(core.rewriter.rewriteSync).to.have.been.calledWith(testFileContent, {
91
- filename: testFilePath,
92
- isModule: false,
93
- inject: true,
94
- wrap: true,
95
- });
96
- expect(core.logger.warn).to.have.been.calledWith(
97
- {
98
- err: sinon.match({
99
- message: 'Missing initializer in const declaration',
100
- }),
101
- },
102
- 'Failed to compile rewritten code for %s, compiling original code.',
103
- testFilePath,
104
- );
105
- expect(complileSpy).to.have.been.calledWith(
106
- testFileContent,
107
- testFilePath,
108
- );
109
- });
110
-
111
- it('throws an error if the original file fails to compile', function () {
112
- core.rewriter.cache.readSync.returns(undefined);
113
- const testFilePath = require.resolve('../test/resources/file-bad');
114
- const testFileContent = readFileSync(testFilePath).toString();
115
-
116
- rewriteHooks.install();
117
-
118
- expect(() => require(testFilePath)).to.throw('Unexpected identifier');
119
- expect(core.rewriter.rewriteSync).to.have.been.calledWith(testFileContent, {
120
- filename: testFilePath,
121
- isModule: false,
122
- inject: true,
123
- wrap: true,
124
- });
125
- expect(core.logger.warn).to.have.been.calledWith(
126
- {
127
- err: sinon.match({
128
- message: sinon.match('Unexpected identifier'),
129
- }),
130
- },
131
- 'Failed to compile rewritten code for %s, compiling original code.',
132
- testFilePath,
133
- );
134
- expect(complileSpy).to.have.been.calledWith(
135
- testFileContent,
136
- testFilePath,
137
- );
138
- });
139
- });
@@ -1,127 +0,0 @@
1
- 'use strict';
2
-
3
- const EventEmitter = require('events');
4
- const { expect } = require('chai');
5
- const sinon = require('sinon');
6
- const { initProtectFixture } = require('@contrast/test/fixtures');
7
- const proxyquire = require('proxyquire');
8
-
9
- describe('agentify sources', function () {
10
- [
11
- {
12
- name: 'http',
13
- expected: {
14
- port: 8080,
15
- protocol: 'http',
16
- serverType: 'http',
17
- },
18
- },
19
- {
20
- name: 'https',
21
- expected: {
22
- port: 8080,
23
- protocol: 'https',
24
- serverType: 'https',
25
- },
26
- },
27
- {
28
- name: 'spdy',
29
- expected: {
30
- port: 8080,
31
- protocol: 'https',
32
- serverType: 'spdy',
33
- },
34
- },
35
- {
36
- name: 'http2',
37
- method: 'createServer',
38
- expected: {
39
- port: 8080,
40
- protocol: 'https',
41
- serverType: 'spdy',
42
- },
43
- },
44
- {
45
- name: 'http2',
46
- method: 'createSecureServer',
47
- expected: {
48
- port: 8080,
49
- protocol: 'https',
50
- serverType: 'spdy',
51
- },
52
- }
53
- ].forEach(({ name, method, expected }) => {
54
- describe(`${name} sources using ${method || 'Server'}()`, function () {
55
- let core, api, ServerMock, reqMock, resMock, onFinishedMock;
56
-
57
- beforeEach(function () {
58
- ({ core } = initProtectFixture());
59
- ServerMock = function ServerMock() {
60
- this.e = new EventEmitter();
61
- };
62
- ServerMock.prototype.emit = function (...args) {
63
- this.e.emit(...args);
64
- };
65
- ServerMock.prototype.on = function (...args) {
66
- this.e.on(...args);
67
- };
68
- api = {
69
- Server: ServerMock,
70
- createServer() {
71
- return new ServerMock();
72
- },
73
- createSecureServer() {
74
- return new ServerMock();
75
- }
76
- };
77
- reqMock = {
78
- socket: {
79
- address() {
80
- return { port: 8080 };
81
- }
82
- },
83
- method: 'GET',
84
- url: 'http://localhost',
85
- };
86
- resMock = new EventEmitter();
87
- onFinishedMock = sinon.stub();
88
-
89
- core.depHooks.resolve.withArgs(sinon.match({ name: 'http' })).yields(api);
90
- proxyquire('./sources', {
91
- 'on-finished': onFinishedMock,
92
- })(core).install();
93
- });
94
-
95
- it('"request" events run in scope with correct sourceInfo', function () {
96
- const server = method ? api[method]() : new ServerMock();
97
- let store;
98
-
99
- server.on('request', function () {
100
- store = core.scopes.sources.getStore();
101
- });
102
-
103
- server.emit('request', reqMock, resMock);
104
-
105
- expect(store.sourceInfo).to.deep.include({
106
- port: 8080,
107
- protocol: 'http',
108
- serverType: 'http',
109
- });
110
-
111
- expect(onFinishedMock).to.have.been.calledWith(resMock);
112
- });
113
-
114
- it('non-"request" events do not run in scope', function () {
115
- const server = method ? api[method]() : new ServerMock();
116
- let store;
117
-
118
- server.on('foo', function () {
119
- store = core.scopes.sources.getStore();
120
- });
121
-
122
- server.emit('foo', reqMock, resMock);
123
- expect(store).to.be.undefined;
124
- });
125
- });
126
- });
127
- });
package/lib/utils.test.js DELETED
@@ -1,110 +0,0 @@
1
- 'use strict';
2
-
3
- const { expect } = require('chai');
4
- const sinon = require('sinon');
5
-
6
- const {
7
- assertValidOpts,
8
- assertSupportedNodeVersion,
9
- assertSupportedPreloadUsage,
10
- assertNoExperimentalFeatureFlags,
11
- } = require('./utils');
12
-
13
- describe('preStartupValidation', function() {
14
-
15
- describe('assertValidOpts', function() {
16
- it('Does not validate when noValidate is true', function() {
17
- const opts = {
18
- noValidate: true
19
- };
20
- expect(assertValidOpts(opts)).to.be.undefined;
21
- });
22
-
23
- it('Throws an error if install order is incorrect', function() {
24
- const opts = {
25
- installOrder: [
26
- 'telemetry',
27
- 'reporter',
28
- 'startupValidation'
29
- ]
30
- };
31
- expect(() => assertValidOpts(opts)).to.throw('The \'installOrder\' option must include \'reporter\' as first element. found \'telemetry\'');
32
- });
33
- });
34
-
35
- describe('assertSupportedNodeVersion', function() {
36
-
37
- let engines;
38
- beforeEach(function() {
39
- engines = '>=16.9.1 <17 || >=18.7.0 <19 || >=20.6.0 <21 || >= 22.5.1 <23';
40
- });
41
-
42
- it('Does not throw an error if process.version is supported', function() {
43
- sinon.stub(process, 'version').value('v22.9.0');
44
- expect(assertSupportedNodeVersion(engines)).to.be.undefined;
45
- });
46
-
47
- it('Throws an error if process.version is unsupported', function() {
48
- sinon.stub(process, 'version').value('v14.0.0');
49
- expect(() => assertSupportedNodeVersion(engines)).to.throw('Contrast only officially supports Node LTS versions between 16.9.1 and 17, 18.7.0 and 19, 20.6.0 and 21, 22.5.1 and 23, but detected v14.0.0');
50
- });
51
-
52
- });
53
-
54
- describe('assertSupportedPreloadUsage', function() {
55
-
56
- it('does not throw an err if --import flag is used (>= 18.19.0)', function() {
57
- sinon.stub(process, 'execArgv').value(['--import', '@contrast/agent']);
58
- sinon.stub(process, 'version').value('v22.9.0');
59
- expect(assertSupportedPreloadUsage()).to.be.true;
60
- });
61
-
62
- it('throws an err if --import flag is used (<= 18.19.0)', function() {
63
- sinon.stub(process, 'execArgv').value(['--import', '@contrast/agent']);
64
- sinon.stub(process, 'version').value('v18.18.0');
65
- expect(() => assertSupportedPreloadUsage()).to.throw('Contrast requires that Node LTS versions >= 18.19.0 use the --import flag for ESM support');
66
- });
67
-
68
- it('throws an err if --loader flag is used (>= 18.19.0)', function() {
69
- sinon.stub(process, 'execArgv').value(['--loader', '@contrast/agent']);
70
- sinon.stub(process, 'version').value('v18.20.0');
71
- expect(() => assertSupportedPreloadUsage()).to.throw('Contrast requires that Node versions less than 18.19.0 use the --loader flag for ESM support');
72
- });
73
-
74
- });
75
-
76
- describe('assertNoExperimentalFeatureFlags', function() {
77
- describe('process.execArgv', function() {
78
- it('Does not throw an error if no experimental feature flag is used', function() {
79
- sinon.stub(process, 'execArgv').value(['--import', '@contrast/agent']);
80
- expect(assertNoExperimentalFeatureFlags()).to.be.undefined;
81
- });
82
-
83
- it('Throws an error if an experimental feature flag is used', function() {
84
- sinon.stub(process, 'execArgv').value(['--experimental-feature', '--import', '@contrast/agent']);
85
- expect(() => assertNoExperimentalFeatureFlags()).to.throw('Contrast Agent does not support experimental features.');
86
- });
87
- });
88
-
89
- describe('NODE_OPTIONS', function() {
90
-
91
- const origNodeOptions = process.env?.NODE_OPTIONS;
92
-
93
- afterEach(function() {
94
- process.env.NODE_OPTIONS = origNodeOptions;
95
- });
96
-
97
- it('Does not throw an error if no experimental feature flag is used', function() {
98
- process.env.NODE_OPTIONS = '--import @contrast/agent';
99
- expect(assertNoExperimentalFeatureFlags()).to.be.undefined;
100
- });
101
-
102
- it('Throws an error if an experimental feature flag is used', function() {
103
- process.env.NODE_OPTIONS = '--experimental-feature --import @contrast/agent';
104
- expect(() => assertNoExperimentalFeatureFlags()).to.throw('Contrast Agent does not support experimental features.');
105
- });
106
- });
107
-
108
- });
109
-
110
- });
@@ -1,78 +0,0 @@
1
- 'use strict';
2
-
3
- const assert = require('node:assert');
4
- const sinon = require('sinon');
5
- const mocks = require('@contrast/test/mocks');
6
- const { IntentionalError } = require('@contrast/common');
7
-
8
- const originalArgv = process.argv;
9
- const originalConsoleWarn = console.warn;
10
- const originalConsoleError = console.error;
11
-
12
- describe('reporter validators: config', function() {
13
- let getEffectiveValue;
14
- let logger;
15
- let tcore;
16
- let validators;
17
-
18
- beforeEach(function() {
19
- // change this to test different scenarios
20
- getEffectiveValue = function() {
21
- return false;
22
- };
23
- logger = mocks.logger();
24
- tcore = {
25
- config: {
26
- enable: true,
27
- getEffectiveValue: (prop) => getEffectiveValue(prop)
28
- },
29
- logger,
30
- };
31
- validators = require('@contrast/agentify/lib/validators.js');
32
-
33
- process.argv = originalArgv.slice();
34
- console.warn = sinon.stub();
35
- console.error = sinon.stub();
36
- });
37
-
38
- after(function() {
39
- console.warn = originalConsoleWarn;
40
- console.error = originalConsoleError;
41
- });
42
-
43
- it('throws IntentionalError (info) when config enable: false', async function() {
44
- tcore.config.enable = false;
45
-
46
- assert.throws(() => validators.config(tcore), (e) => {
47
- assert(logger.warn.notCalled);
48
- assert(e instanceof IntentionalError);
49
- assert.match(e.message, /Contrast agent disabled by configuration/);
50
- return true;
51
- });
52
- });
53
-
54
- ['-c', '--configFile'].forEach(function(tflag) {
55
- ['Contrast_Security.yml', 'contrast-security.yaml', 'app.cfg'].forEach(function(farg) {
56
- const shouldWarn = farg !== 'app.cfg';
57
- const msg = shouldWarn ? `logs ${tflag} ${farg}` : `does not log ${tflag} ${farg}`;
58
-
59
- it(`${msg} before an Error is thrown`, async function() {
60
- tcore.config.enable = false;
61
- process.argv.push(tflag, farg);
62
-
63
- assert.throws(() => validators.config(tcore), (e) => {
64
- if (shouldWarn) {
65
- assert(logger.warn.calledOnceWith(`Command line config flag is not supported: ${tflag} ${farg}`));
66
- } else {
67
- assert(logger.warn.notCalled);
68
- }
69
-
70
- assert(e instanceof Error);
71
- assert.match(e.message, /Contrast agent disabled by configuration/);
72
- return true;
73
- });
74
- });
75
- });
76
- });
77
- });
78
-