doc-detective 3.5.0-dev.0 → 3.5.0-dev.1

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.
Files changed (44) hide show
  1. package/.github/FUNDING.yml +14 -14
  2. package/.github/dependabot.yml +11 -11
  3. package/.github/workflows/auto-dev-release.yml +173 -173
  4. package/.github/workflows/npm-test.yaml +95 -96
  5. package/.github/workflows/update-core.yaml +131 -131
  6. package/CONTRIBUTIONS.md +27 -27
  7. package/LICENSE +661 -661
  8. package/README.md +110 -110
  9. package/dev/dev.config.json +3 -8
  10. package/dev/dev.spec.json +30 -30
  11. package/dev/index.js +5 -5
  12. package/package.json +47 -47
  13. package/reference.png +0 -0
  14. package/samples/.doc-detective.json +94 -94
  15. package/samples/doc-content-detect.md +10 -10
  16. package/samples/doc-content-inline-tests.md +23 -23
  17. package/samples/docker-hello.spec.json +15 -15
  18. package/samples/env +2 -2
  19. package/samples/http.spec.yaml +37 -37
  20. package/samples/kitten-search-detect.md +7 -7
  21. package/samples/kitten-search-inline.md +15 -15
  22. package/samples/kitten-search.spec.json +28 -28
  23. package/samples/local-gui.md +5 -5
  24. package/samples/tests.spec.json +70 -70
  25. package/samples/variables.env +4 -4
  26. package/scripts/bump-sync-version-core.js +108 -110
  27. package/src/checkDependencies.js +84 -84
  28. package/src/index.js +72 -72
  29. package/src/utils.js +1023 -1023
  30. package/test/artifacts/cleanup.spec.json +18 -18
  31. package/test/artifacts/config.json +6 -6
  32. package/test/artifacts/doc-content.md +23 -23
  33. package/test/artifacts/env +2 -2
  34. package/test/artifacts/httpRequest.spec.yaml +37 -37
  35. package/test/artifacts/runShell.spec.json +29 -29
  36. package/test/artifacts/setup.spec.json +18 -18
  37. package/test/artifacts/test.spec.json +46 -46
  38. package/test/resolvedTests.test.js +193 -193
  39. package/test/runTests.test.js +53 -53
  40. package/test/server/index.js +185 -185
  41. package/test/server/public/index.html +174 -174
  42. package/test/test-config.json +12 -12
  43. package/test/test-results.json +124 -124
  44. package/test/utils.test.js +634 -298
@@ -1,298 +1,634 @@
1
- const { setArgs, setConfig, outputResults } = require("../src/utils");
2
- const path = require("path");
3
- const fs = require("fs");
4
-
5
- before(async function () {
6
- const { expect } = await import("chai");
7
- global.expect = expect;
8
- });
9
-
10
- describe("Util tests", function () {
11
- // Test that arguments are parsed correctly
12
- it("Yargs parses arguments correctly", function () {
13
- const argSets = [
14
- {
15
- args: ["node", "runTests.js", "--input", "input.spec.json"],
16
- expected: { i: "input.spec.json" },
17
- },
18
- {
19
- args: [
20
- "node",
21
- "runTests.js",
22
- "--input",
23
- "input.spec.json",
24
- "--logLevel",
25
- "debug",
26
- ],
27
- expected: { i: "input.spec.json", l: "debug" },
28
- },
29
- {
30
- args: [
31
- "node",
32
- "runTests.js",
33
- "--input",
34
- "input.spec.json",
35
- "--logLevel",
36
- "debug",
37
- "--config",
38
- "config.json",
39
- ],
40
-
41
- expected: { i: "input.spec.json", l: "debug", c: "config.json" },
42
- },
43
- {
44
- args: [
45
- "node",
46
- "runTests.js",
47
- "--input",
48
- "input.spec.json",
49
- "--output",
50
- ".",
51
- "--logLevel",
52
- "debug",
53
- "--config",
54
- "config.json",
55
- ],
56
- expected: {
57
- i: "input.spec.json",
58
- o: ".",
59
- l: "debug",
60
- c: "config.json",
61
- },
62
- },
63
- {
64
- args: [
65
- "node",
66
- "runTests.js",
67
- "--input",
68
- "input.spec.json",
69
- "--allow-unsafe",
70
- ],
71
- expected: {
72
- i: "input.spec.json",
73
- allowUnsafe: true,
74
- },
75
- },
76
- ];
77
- argSets.forEach((argSet) => {
78
- expect(setArgs(argSet.args)).to.deep.include(argSet.expected);
79
- });
80
- });
81
-
82
- // Test that config overrides are set correctly
83
- it("Config overrides are set correctly", async function () {
84
- // This test takes a bit longer
85
- this.timeout(5000);
86
-
87
- configSets = [
88
- {
89
- // Input override
90
- args: ["node", "runTests.js", "--input", "input.spec.json"],
91
- expected: { input: [path.resolve(process.cwd(), "input.spec.json")] },
92
- },
93
- {
94
- // Input and logLevel overrides
95
- args: [
96
- "node",
97
- "runTests.js",
98
- "--input",
99
- "input.spec.json",
100
- "--logLevel",
101
- "debug",
102
- ],
103
- expected: { input: [path.resolve(process.cwd(), "input.spec.json")], logLevel: "debug" },
104
- },
105
- {
106
- // Input, logLevel, and setup overrides
107
- args: [
108
- "node",
109
- "runTests.js",
110
- "--input",
111
- "input.spec.json",
112
- "--logLevel",
113
- "debug",
114
- ],
115
- expected: {
116
- input: [path.resolve(process.cwd(), "input.spec.json")],
117
- logLevel: "debug",
118
- },
119
- },
120
- {
121
- // Referenced config without overrides
122
- args: ["node", "runTests.js", "--config", "./test/test-config.json"],
123
- expected: {
124
- input: process.cwd(),
125
- logLevel: "silent",
126
- recursive: true,
127
- },
128
- },
129
- {
130
- // Referenced config with overrides
131
- args: [
132
- "node",
133
- "runTests.js",
134
- "--config",
135
- "./test/test-config.json",
136
- "--input",
137
- "input.spec.json",
138
- ],
139
- expected: {
140
- input: [path.resolve(process.cwd(), "input.spec.json")],
141
- logLevel: "silent",
142
- recursive: true,
143
- },
144
- },
145
- {
146
- // Multiple inputs
147
- args: [
148
- "node",
149
- "runTests.js",
150
- "--config",
151
- "./test/test-config.json",
152
- "--input",
153
- "input.spec.json,anotherInput.spec.json",
154
- ],
155
- expected: {
156
- input: [path.resolve(process.cwd(), "input.spec.json"), path.resolve(process.cwd(), "anotherInput.spec.json")],
157
- output: process.cwd(),
158
- recursive: true,
159
- },
160
- },
161
- {
162
- // allow-unsafe override
163
- args: [
164
- "node",
165
- "runTests.js",
166
- "--input",
167
- "input.spec.json",
168
- "--allow-unsafe",
169
- ],
170
- expected: {
171
- input: [path.resolve(process.cwd(), "input.spec.json")],
172
- allowUnsafeSteps: true,
173
- },
174
- }
175
- ];
176
-
177
- // Use process.stdout.write directly to force console output during tests
178
- console.log('\n===== CONFIG TEST RESULTS =====\n');
179
-
180
- // Use Promise.all with map instead of forEach to properly handle async operations
181
- await Promise.all(configSets.map(async (configSet, index) => {
182
- // Set config with the args
183
- console.log(`Config test ${index}: ${JSON.stringify(configSet, null, 2)}`);
184
- const configResult = await setConfig({ args: setArgs(configSet.args) });
185
- console.log(`Config result ${index}: ${JSON.stringify(configResult, null, 2)}\n`);
186
- // Deeply compare the config result with the expected result
187
- deepObjectExpect(configResult, configSet.expected);
188
- }));
189
- process.stdout.write('===== END CONFIG TEST RESULTS =====\n');
190
- });
191
-
192
- // Test that results output correctly.
193
- it("Results output correctly", async () => {
194
- // Output test-results.json, make sure it exists, and clean it up.
195
- const inputResultsPath = path.resolve("./test/test-results.json");
196
- const inputResultsJSON = require(inputResultsPath);
197
- const outputResultsPath = path.resolve("./test/output-test-results.json");
198
- // Check that input file exists
199
- expect(fs.existsSync(inputResultsPath)).to.equal(true);
200
- // Output results
201
- await outputResults(null, outputResultsPath, inputResultsJSON);
202
- // Check that output file exists
203
- expect(fs.existsSync(outputResultsPath)).to.equal(true);
204
- // Clean up
205
- fs.unlinkSync(outputResultsPath);
206
- });
207
-
208
- // Test environment variable config detection
209
- it("Config from DOC_DETECTIVE_CONFIG environment variable is loaded and merged", async function () {
210
- this.timeout(5000);
211
-
212
- // Save the original environment variable value
213
- const originalEnvConfig = process.env.DOC_DETECTIVE_CONFIG;
214
-
215
- try {
216
- // Test 1: Valid environment variable config without file config
217
- process.env.DOC_DETECTIVE_CONFIG = JSON.stringify({
218
- logLevel: "debug"
219
- });
220
-
221
- const config1 = await setConfig({ args: setArgs(["node", "runTests.js"]) });
222
- expect(config1.logLevel).to.equal("debug");
223
-
224
- // Test 2: Environment variable config merged with file config (env var takes precedence)
225
- process.env.DOC_DETECTIVE_CONFIG = JSON.stringify({
226
- logLevel: "error"
227
- });
228
-
229
- const config2 = await setConfig({
230
- configPath: "./test/test-config.json",
231
- args: setArgs(["node", "runTests.js", "--config", "./test/test-config.json"])
232
- });
233
- // Environment variable should override file config
234
- expect(config2.logLevel).to.equal("error");
235
- // Check that other values from file config are preserved
236
- expect(config2.telemetry.send).to.equal(false);
237
-
238
- // Test 3: Environment variable config with command line args (args take precedence)
239
- process.env.DOC_DETECTIVE_CONFIG = JSON.stringify({
240
- logLevel: "warning",
241
- input: "env-input.json"
242
- });
243
-
244
- const config3 = await setConfig({
245
- args: setArgs(["node", "runTests.js", "--input", "cli-input.json", "--logLevel", "debug"])
246
- });
247
- // Command line args should override environment variable
248
- expect(config3.logLevel).to.equal("debug");
249
- expect(config3.input).to.deep.equal([path.resolve(process.cwd(), "cli-input.json")]);
250
-
251
- } finally {
252
- // Restore the original environment variable value
253
- if (originalEnvConfig !== undefined) {
254
- process.env.DOC_DETECTIVE_CONFIG = originalEnvConfig;
255
- } else {
256
- delete process.env.DOC_DETECTIVE_CONFIG;
257
- }
258
- }
259
- });
260
- });
261
-
262
- // Deeply compares two objects
263
- function deepObjectExpect(actual, expected) {
264
- // Check that actual has all the keys of expected
265
- Object.entries(expected).forEach(([key, value]) => {
266
- // Make sure the property exists in actual
267
- expect(actual).to.have.property(key);
268
-
269
- // If value is null, check directly
270
- if (value === null) {
271
- expect(actual[key]).to.equal(null);
272
- }
273
- // If value is an array, check each item
274
- else if (Array.isArray(value)) {
275
- expect(Array.isArray(actual[key])).to.equal(true, `Expected ${key} to be an array. Expected: ${expected[key]}. Actual: ${actual[key]}.`);
276
- expect(actual[key].length).to.equal(value.length, `Expected ${key} array to have length ${value.length}. Actual: ${actual[key].length}`);
277
-
278
- // Check each array item
279
- value.forEach((item, index) => {
280
- if (typeof item === "object" && item !== null) {
281
- deepObjectExpect(actual[key][index], item);
282
- } else {
283
- expect(actual[key][index]).to.equal(item);
284
- }
285
- });
286
- }
287
- // If value is an object but not null, recursively check it
288
- else if (typeof value === "object") {
289
- deepObjectExpect(actual[key], expected[key]);
290
- }
291
- // Otherwise, check that the value is correct
292
- else {
293
- const expectedObject = {};
294
- expectedObject[key] = value;
295
- expect(actual).to.deep.include(expectedObject);
296
- }
297
- });
298
- }
1
+ const { setArgs, setConfig, outputResults } = require("../src/utils");
2
+ const path = require("path");
3
+ const fs = require("fs");
4
+
5
+ before(async function () {
6
+ const { expect } = await import("chai");
7
+ global.expect = expect;
8
+ });
9
+
10
+ describe("Util tests", function () {
11
+ // Test that arguments are parsed correctly
12
+ it("Yargs parses arguments correctly", function () {
13
+ const argSets = [
14
+ {
15
+ args: ["node", "runTests.js", "--input", "input.spec.json"],
16
+ expected: { i: "input.spec.json" },
17
+ },
18
+ {
19
+ args: [
20
+ "node",
21
+ "runTests.js",
22
+ "--input",
23
+ "input.spec.json",
24
+ "--logLevel",
25
+ "debug",
26
+ ],
27
+ expected: { i: "input.spec.json", l: "debug" },
28
+ },
29
+ {
30
+ args: [
31
+ "node",
32
+ "runTests.js",
33
+ "--input",
34
+ "input.spec.json",
35
+ "--logLevel",
36
+ "debug",
37
+ "--config",
38
+ "config.json",
39
+ ],
40
+
41
+ expected: { i: "input.spec.json", l: "debug", c: "config.json" },
42
+ },
43
+ {
44
+ args: [
45
+ "node",
46
+ "runTests.js",
47
+ "--input",
48
+ "input.spec.json",
49
+ "--output",
50
+ ".",
51
+ "--logLevel",
52
+ "debug",
53
+ "--config",
54
+ "config.json",
55
+ ],
56
+ expected: {
57
+ i: "input.spec.json",
58
+ o: ".",
59
+ l: "debug",
60
+ c: "config.json",
61
+ },
62
+ },
63
+ {
64
+ args: [
65
+ "node",
66
+ "runTests.js",
67
+ "--input",
68
+ "input.spec.json",
69
+ "--allow-unsafe",
70
+ ],
71
+ expected: {
72
+ i: "input.spec.json",
73
+ allowUnsafe: true,
74
+ },
75
+ },
76
+ ];
77
+ argSets.forEach((argSet) => {
78
+ expect(setArgs(argSet.args)).to.deep.include(argSet.expected);
79
+ });
80
+ });
81
+
82
+ // Test that config overrides are set correctly
83
+ it("Config overrides are set correctly", async function () {
84
+ // This test takes a bit longer
85
+ this.timeout(5000);
86
+
87
+ configSets = [
88
+ {
89
+ // Input override
90
+ args: ["node", "runTests.js", "--input", "input.spec.json"],
91
+ expected: { input: [path.resolve(process.cwd(), "input.spec.json")] },
92
+ },
93
+ {
94
+ // Input and logLevel overrides
95
+ args: [
96
+ "node",
97
+ "runTests.js",
98
+ "--input",
99
+ "input.spec.json",
100
+ "--logLevel",
101
+ "debug",
102
+ ],
103
+ expected: {
104
+ input: [path.resolve(process.cwd(), "input.spec.json")],
105
+ logLevel: "debug",
106
+ },
107
+ },
108
+ {
109
+ // Input, logLevel, and setup overrides
110
+ args: [
111
+ "node",
112
+ "runTests.js",
113
+ "--input",
114
+ "input.spec.json",
115
+ "--logLevel",
116
+ "debug",
117
+ ],
118
+ expected: {
119
+ input: [path.resolve(process.cwd(), "input.spec.json")],
120
+ logLevel: "debug",
121
+ },
122
+ },
123
+ {
124
+ // Referenced config without overrides
125
+ args: ["node", "runTests.js", "--config", "./test/test-config.json"],
126
+ expected: {
127
+ input: process.cwd(),
128
+ logLevel: "silent",
129
+ recursive: true,
130
+ },
131
+ },
132
+ {
133
+ // Referenced config with overrides
134
+ args: [
135
+ "node",
136
+ "runTests.js",
137
+ "--config",
138
+ "./test/test-config.json",
139
+ "--input",
140
+ "input.spec.json",
141
+ ],
142
+ expected: {
143
+ input: [path.resolve(process.cwd(), "input.spec.json")],
144
+ logLevel: "silent",
145
+ recursive: true,
146
+ },
147
+ },
148
+ {
149
+ // Multiple inputs
150
+ args: [
151
+ "node",
152
+ "runTests.js",
153
+ "--config",
154
+ "./test/test-config.json",
155
+ "--input",
156
+ "input.spec.json,anotherInput.spec.json",
157
+ ],
158
+ expected: {
159
+ input: [
160
+ path.resolve(process.cwd(), "input.spec.json"),
161
+ path.resolve(process.cwd(), "anotherInput.spec.json"),
162
+ ],
163
+ output: process.cwd(),
164
+ recursive: true,
165
+ },
166
+ },
167
+ {
168
+ // allow-unsafe override
169
+ args: [
170
+ "node",
171
+ "runTests.js",
172
+ "--input",
173
+ "input.spec.json",
174
+ "--allow-unsafe",
175
+ ],
176
+ expected: {
177
+ input: [path.resolve(process.cwd(), "input.spec.json")],
178
+ allowUnsafeSteps: true,
179
+ },
180
+ },
181
+ ];
182
+
183
+ // Use process.stdout.write directly to force console output during tests
184
+ console.log("\n===== CONFIG TEST RESULTS =====\n");
185
+
186
+ // Use Promise.all with map instead of forEach to properly handle async operations
187
+ await Promise.all(
188
+ configSets.map(async (configSet, index) => {
189
+ // Set config with the args
190
+ console.log(
191
+ `Config test ${index}: ${JSON.stringify(configSet, null, 2)}`
192
+ );
193
+ const configResult = await setConfig({ args: setArgs(configSet.args) });
194
+ console.log(
195
+ `Config result ${index}: ${JSON.stringify(configResult, null, 2)}\n`
196
+ );
197
+ // Deeply compare the config result with the expected result
198
+ deepObjectExpect(configResult, configSet.expected);
199
+ })
200
+ );
201
+ process.stdout.write("===== END CONFIG TEST RESULTS =====\n");
202
+ });
203
+
204
+ // Test that results output correctly.
205
+ it("Results output correctly", async () => {
206
+ // Output test-results.json, make sure it exists, and clean it up.
207
+ const inputResultsPath = path.resolve("./test/test-results.json");
208
+ const inputResultsJSON = require(inputResultsPath);
209
+ const outputResultsPath = path.resolve("./test/output-test-results.json");
210
+ // Check that input file exists
211
+ expect(fs.existsSync(inputResultsPath)).to.equal(true);
212
+ // Output results
213
+ await outputResults(null, outputResultsPath, inputResultsJSON);
214
+ // Check that output file exists
215
+ expect(fs.existsSync(outputResultsPath)).to.equal(true);
216
+ // Clean up
217
+ fs.unlinkSync(outputResultsPath);
218
+ });
219
+
220
+ // Test environment variable config detection
221
+ it("Config from DOC_DETECTIVE_CONFIG environment variable is loaded and merged", async function () {
222
+ this.timeout(5000);
223
+
224
+ // Save the original environment variable value
225
+ const originalEnvConfig = process.env.DOC_DETECTIVE_CONFIG;
226
+
227
+ try {
228
+ // Ensure env override is not set for the default-value test
229
+ delete process.env.DOC_DETECTIVE_CONFIG;
230
+
231
+ // Test 1: Valid environment variable config without file config
232
+ process.env.DOC_DETECTIVE_CONFIG = JSON.stringify({
233
+ logLevel: "debug",
234
+ });
235
+
236
+ const config1 = await setConfig({
237
+ args: setArgs(["node", "runTests.js"]),
238
+ });
239
+ expect(config1.logLevel).to.equal("debug");
240
+
241
+ // Test 2: Environment variable config merged with file config (env var takes precedence)
242
+ process.env.DOC_DETECTIVE_CONFIG = JSON.stringify({
243
+ logLevel: "error",
244
+ });
245
+
246
+ const config2 = await setConfig({
247
+ configPath: "./test/test-config.json",
248
+ args: setArgs([
249
+ "node",
250
+ "runTests.js",
251
+ "--config",
252
+ "./test/test-config.json",
253
+ ]),
254
+ });
255
+ // Environment variable should override file config
256
+ expect(config2.logLevel).to.equal("error");
257
+ // Check that other values from file config are preserved
258
+ expect(config2.telemetry.send).to.equal(false);
259
+
260
+ // Test 3: Environment variable config with command line args (args take precedence)
261
+ process.env.DOC_DETECTIVE_CONFIG = JSON.stringify({
262
+ logLevel: "warning",
263
+ input: "env-input.json",
264
+ });
265
+
266
+ const config3 = await setConfig({
267
+ args: setArgs([
268
+ "node",
269
+ "runTests.js",
270
+ "--input",
271
+ "cli-input.json",
272
+ "--logLevel",
273
+ "debug",
274
+ ]),
275
+ });
276
+ // Command line args should override environment variable
277
+ expect(config3.logLevel).to.equal("debug");
278
+ expect(config3.input).to.deep.equal([
279
+ path.resolve(process.cwd(), "cli-input.json"),
280
+ ]);
281
+ } finally {
282
+ // Restore the original environment variable value
283
+ if (originalEnvConfig !== undefined) {
284
+ process.env.DOC_DETECTIVE_CONFIG = originalEnvConfig;
285
+ } else {
286
+ delete process.env.DOC_DETECTIVE_CONFIG;
287
+ }
288
+ }
289
+ });
290
+
291
+ // Test that false values for recursive and detectSteps are preserved
292
+ it("Preserves false values for recursive and detectSteps config properties", async function () {
293
+ this.timeout(5000);
294
+
295
+ // Save original environment variable
296
+ const originalEnvConfig = process.env.DOC_DETECTIVE_CONFIG;
297
+
298
+ try {
299
+ // Test 1: Default values when not specified (should be true)
300
+ const config1 = await setConfig({
301
+ args: setArgs(["node", "runTests.js"]),
302
+ });
303
+ expect(config1.recursive).to.equal(
304
+ true,
305
+ "recursive should default to true"
306
+ );
307
+ expect(config1.detectSteps).to.equal(
308
+ true,
309
+ "detectSteps should default to true"
310
+ );
311
+
312
+ // Test 2: Explicitly set to false in environment variable
313
+ process.env.DOC_DETECTIVE_CONFIG = JSON.stringify({
314
+ recursive: false,
315
+ detectSteps: false,
316
+ });
317
+
318
+ const config2 = await setConfig({
319
+ args: setArgs(["node", "runTests.js"]),
320
+ });
321
+ expect(config2.recursive).to.equal(
322
+ false,
323
+ "recursive should be false from env var"
324
+ );
325
+ expect(config2.detectSteps).to.equal(
326
+ false,
327
+ "detectSteps should be false from env var"
328
+ );
329
+
330
+ // Test 3: Explicitly set to true in environment variable
331
+ process.env.DOC_DETECTIVE_CONFIG = JSON.stringify({
332
+ recursive: true,
333
+ detectSteps: true,
334
+ });
335
+
336
+ const config3 = await setConfig({
337
+ args: setArgs(["node", "runTests.js"]),
338
+ });
339
+ expect(config3.recursive).to.equal(
340
+ true,
341
+ "recursive should be true from env var"
342
+ );
343
+ expect(config3.detectSteps).to.equal(
344
+ true,
345
+ "detectSteps should be true from env var"
346
+ );
347
+
348
+ // Test 4: Only one set to false
349
+ process.env.DOC_DETECTIVE_CONFIG = JSON.stringify({
350
+ recursive: false,
351
+ });
352
+
353
+ const config4 = await setConfig({
354
+ args: setArgs(["node", "runTests.js"]),
355
+ });
356
+ expect(config4.recursive).to.equal(
357
+ false,
358
+ "recursive should be false from env var"
359
+ );
360
+ expect(config4.detectSteps).to.equal(
361
+ true,
362
+ "detectSteps should default to true"
363
+ );
364
+
365
+ // Test 5: Only detectSteps set to false
366
+ process.env.DOC_DETECTIVE_CONFIG = JSON.stringify({
367
+ detectSteps: false,
368
+ });
369
+
370
+ const config5 = await setConfig({
371
+ args: setArgs(["node", "runTests.js"]),
372
+ });
373
+ expect(config5.recursive).to.equal(
374
+ true,
375
+ "recursive should default to true"
376
+ );
377
+ expect(config5.detectSteps).to.equal(
378
+ false,
379
+ "detectSteps should be false from env var"
380
+ );
381
+ } finally {
382
+ // Restore the original environment variable value
383
+ if (originalEnvConfig !== undefined) {
384
+ process.env.DOC_DETECTIVE_CONFIG = originalEnvConfig;
385
+ } else {
386
+ delete process.env.DOC_DETECTIVE_CONFIG;
387
+ }
388
+ }
389
+ });
390
+
391
+ // Test that false values from config file are preserved
392
+ it("Preserves false values for recursive and detectSteps from config file", async function () {
393
+ this.timeout(5000);
394
+
395
+ const testConfigDir = path.resolve("./test");
396
+
397
+ // Create temporary config files for testing
398
+ const configWithFalseValues = path.join(
399
+ testConfigDir,
400
+ "test-config-false-values.json"
401
+ );
402
+ const configWithTrueValues = path.join(
403
+ testConfigDir,
404
+ "test-config-true-values.json"
405
+ );
406
+ const configWithMixedValues = path.join(
407
+ testConfigDir,
408
+ "test-config-mixed-values.json"
409
+ );
410
+
411
+ try {
412
+ // Create config file with both set to false
413
+ fs.writeFileSync(
414
+ configWithFalseValues,
415
+ JSON.stringify(
416
+ {
417
+ recursive: false,
418
+ detectSteps: false,
419
+ logLevel: "silent",
420
+ },
421
+ null,
422
+ 2
423
+ )
424
+ );
425
+
426
+ // Create config file with both set to true
427
+ fs.writeFileSync(
428
+ configWithTrueValues,
429
+ JSON.stringify(
430
+ {
431
+ recursive: true,
432
+ detectSteps: true,
433
+ logLevel: "silent",
434
+ },
435
+ null,
436
+ 2
437
+ )
438
+ );
439
+
440
+ // Create config file with mixed values
441
+ fs.writeFileSync(
442
+ configWithMixedValues,
443
+ JSON.stringify(
444
+ {
445
+ recursive: false,
446
+ detectSteps: true,
447
+ logLevel: "silent",
448
+ },
449
+ null,
450
+ 2
451
+ )
452
+ );
453
+
454
+ // Test 1: Config file with false values
455
+ const config1 = await setConfig({
456
+ configPath: configWithFalseValues,
457
+ args: setArgs([
458
+ "node",
459
+ "runTests.js",
460
+ "--config",
461
+ configWithFalseValues,
462
+ ]),
463
+ });
464
+ expect(config1.recursive).to.equal(
465
+ false,
466
+ "recursive should be false from config file"
467
+ );
468
+ expect(config1.detectSteps).to.equal(
469
+ false,
470
+ "detectSteps should be false from config file"
471
+ );
472
+
473
+ // Test 2: Config file with true values
474
+ const config2 = await setConfig({
475
+ configPath: configWithTrueValues,
476
+ args: setArgs([
477
+ "node",
478
+ "runTests.js",
479
+ "--config",
480
+ configWithTrueValues,
481
+ ]),
482
+ });
483
+ expect(config2.recursive).to.equal(
484
+ true,
485
+ "recursive should be true from config file"
486
+ );
487
+ expect(config2.detectSteps).to.equal(
488
+ true,
489
+ "detectSteps should be true from config file"
490
+ );
491
+
492
+ // Test 3: Config file with mixed values
493
+ const config3 = await setConfig({
494
+ configPath: configWithMixedValues,
495
+ args: setArgs([
496
+ "node",
497
+ "runTests.js",
498
+ "--config",
499
+ configWithMixedValues,
500
+ ]),
501
+ });
502
+ expect(config3.recursive).to.equal(
503
+ false,
504
+ "recursive should be false from config file"
505
+ );
506
+ expect(config3.detectSteps).to.equal(
507
+ true,
508
+ "detectSteps should be true from config file"
509
+ );
510
+ } finally {
511
+ // Clean up temporary config files
512
+ if (fs.existsSync(configWithFalseValues)) {
513
+ fs.unlinkSync(configWithFalseValues);
514
+ }
515
+ if (fs.existsSync(configWithTrueValues)) {
516
+ fs.unlinkSync(configWithTrueValues);
517
+ }
518
+ if (fs.existsSync(configWithMixedValues)) {
519
+ fs.unlinkSync(configWithMixedValues);
520
+ }
521
+ }
522
+ });
523
+
524
+ // Test that environment variable overrides config file for recursive and detectSteps
525
+ it("Environment variable overrides config file for recursive and detectSteps", async function () {
526
+ this.timeout(5000);
527
+
528
+ const testConfigDir = path.resolve("./test");
529
+ const testConfigPath = path.join(
530
+ testConfigDir,
531
+ "test-config-override.json"
532
+ );
533
+
534
+ // Save original environment variable
535
+ const originalEnvConfig = process.env.DOC_DETECTIVE_CONFIG;
536
+
537
+ try {
538
+ // Create config file with true values
539
+ fs.writeFileSync(
540
+ testConfigPath,
541
+ JSON.stringify(
542
+ {
543
+ recursive: true,
544
+ detectSteps: true,
545
+ logLevel: "silent",
546
+ },
547
+ null,
548
+ 2
549
+ )
550
+ );
551
+
552
+ // Set environment variable with false values
553
+ process.env.DOC_DETECTIVE_CONFIG = JSON.stringify({
554
+ recursive: false,
555
+ detectSteps: false,
556
+ });
557
+
558
+ // Environment variable should override file config
559
+ const config = await setConfig({
560
+ configPath: testConfigPath,
561
+ args: setArgs(["node", "runTests.js", "--config", testConfigPath]),
562
+ });
563
+
564
+ expect(config.recursive).to.equal(
565
+ false,
566
+ "recursive should be false from env var (overriding config file)"
567
+ );
568
+ expect(config.detectSteps).to.equal(
569
+ false,
570
+ "detectSteps should be false from env var (overriding config file)"
571
+ );
572
+ expect(config.logLevel).to.equal(
573
+ "silent",
574
+ "logLevel should be preserved from config file"
575
+ );
576
+ } finally {
577
+ // Clean up
578
+ if (fs.existsSync(testConfigPath)) {
579
+ fs.unlinkSync(testConfigPath);
580
+ }
581
+
582
+ // Restore the original environment variable value
583
+ if (originalEnvConfig !== undefined) {
584
+ process.env.DOC_DETECTIVE_CONFIG = originalEnvConfig;
585
+ } else {
586
+ delete process.env.DOC_DETECTIVE_CONFIG;
587
+ }
588
+ }
589
+ });
590
+ });
591
+
592
+ // Deeply compares two objects
593
+ function deepObjectExpect(actual, expected) {
594
+ // Check that actual has all the keys of expected
595
+ Object.entries(expected).forEach(([key, value]) => {
596
+ // Make sure the property exists in actual
597
+ expect(actual).to.have.property(key);
598
+
599
+ // If value is null, check directly
600
+ if (value === null) {
601
+ expect(actual[key]).to.equal(null);
602
+ }
603
+ // If value is an array, check each item
604
+ else if (Array.isArray(value)) {
605
+ expect(Array.isArray(actual[key])).to.equal(
606
+ true,
607
+ `Expected ${key} to be an array. Expected: ${expected[key]}. Actual: ${actual[key]}.`
608
+ );
609
+ expect(actual[key].length).to.equal(
610
+ value.length,
611
+ `Expected ${key} array to have length ${value.length}. Actual: ${actual[key].length}`
612
+ );
613
+
614
+ // Check each array item
615
+ value.forEach((item, index) => {
616
+ if (typeof item === "object" && item !== null) {
617
+ deepObjectExpect(actual[key][index], item);
618
+ } else {
619
+ expect(actual[key][index]).to.equal(item);
620
+ }
621
+ });
622
+ }
623
+ // If value is an object but not null, recursively check it
624
+ else if (typeof value === "object") {
625
+ deepObjectExpect(actual[key], expected[key]);
626
+ }
627
+ // Otherwise, check that the value is correct
628
+ else {
629
+ const expectedObject = {};
630
+ expectedObject[key] = value;
631
+ expect(actual).to.deep.include(expectedObject);
632
+ }
633
+ });
634
+ }