doc-detective 3.4.0-dev.1 → 3.4.0-dev.10

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.
@@ -0,0 +1,9 @@
1
+ {
2
+ "input": "dev.spec.json",
3
+ "logLevel": "debug",
4
+ "integrations": {
5
+ "docDetectiveApi": {
6
+ "apiKey": "cd6f10bf0a47db1436cb3294b54e4f3e1599ca64be0313039fb0fc474ec8d116"
7
+ }
8
+ }
9
+ }
package/dev/dev.spec.json CHANGED
@@ -1,45 +1,19 @@
1
1
  {
2
- "id": "Do all the things! - Spec",
3
2
  "tests": [
4
3
  {
5
- "id": "Do all the things! - Test",
6
- "description": "This test includes nearly every property across all actions.",
7
4
  "steps": [
8
- {
9
- "action": "setVariables",
10
- "path": ".env"
11
- },
12
5
  {
13
6
  "action": "runShell",
14
- "command": "echo",
15
- "args": ["$USER"]
7
+ "command": "echo Hello, World!"
16
8
  },
17
9
  {
18
10
  "action": "checkLink",
19
11
  "url": "https://www.duckduckgo.com"
20
12
  },
21
- {
22
- "action": "httpRequest",
23
- "url": "https://reqres.in/api/users",
24
- "method": "post",
25
- "requestData": {
26
- "name": "morpheus",
27
- "job": "leader"
28
- },
29
- "responseData": {
30
- "name": "morpheus",
31
- "job": "leader"
32
- },
33
- "statusCodes": [200, 201]
34
- },
35
13
  {
36
14
  "action": "goTo",
37
15
  "url": "https://www.google.com"
38
16
  },
39
- {
40
- "action": "startRecording",
41
- "path": "test.mp4"
42
- },
43
17
  {
44
18
  "action": "find",
45
19
  "selector": "[title=Search]",
@@ -49,12 +23,6 @@
49
23
  "typeKeys": {
50
24
  "keys": ["shorthair cat", "$ENTER$"]
51
25
  }
52
- },
53
- {
54
- "action": "wait"
55
- },
56
- {
57
- "action": "stopRecording"
58
26
  }
59
27
  ]
60
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doc-detective",
3
- "version": "3.4.0-dev.1",
3
+ "version": "3.4.0-dev.10",
4
4
  "description": "Treat doc content as testable assertions to validate doc accuracy and product UX.",
5
5
  "bin": {
6
6
  "doc-detective": "src/index.js"
@@ -33,8 +33,9 @@
33
33
  "homepage": "https://github.com/doc-detective/doc-detective#readme",
34
34
  "dependencies": {
35
35
  "@ffmpeg-installer/ffmpeg": "^1.1.0",
36
- "doc-detective-common": "^3.4.0-dita.0-dev.1",
37
- "doc-detective-core": "^3.4.0-dita.0-dev.1",
36
+ "axios": "^1.12.2",
37
+ "doc-detective-common": "3.4.0-dev.2",
38
+ "doc-detective-core": "3.4.0-dev.6",
38
39
  "yargs": "^17.7.2"
39
40
  },
40
41
  "devDependencies": {
package/reference.png CHANGED
Binary file
@@ -20,8 +20,8 @@ function execCommand(command, options = {}) {
20
20
 
21
21
  function main() {
22
22
  // Clean git state
23
- execCommand("git checkout -- .");
24
- execCommand("git clean -fd");
23
+ execCommand("git checkout -- .");
24
+ execCommand("git clean -fd");
25
25
 
26
26
  // Get current project version
27
27
  const packageJsonPath = path.join(process.cwd(), "package.json");
@@ -54,42 +54,40 @@ function main() {
54
54
  // Extract major and minor versions using semver
55
55
  const projMajor = semver.major(projVersion);
56
56
  const projMinor = semver.minor(projVersion);
57
+ const projPatch = semver.patch(projVersion);
57
58
  const coreMajor = semver.major(coreVersion);
58
59
  const coreMinor = semver.minor(coreVersion);
60
+ const corePatch = semver.patch(coreVersion);
59
61
 
60
- console.log(`Project version: ${projMajor}.${projMinor}.x`);
61
- console.log(`core version: ${coreMajor}.${coreMinor}.x`);
62
+ console.log(`Project version: ${projMajor}.${projMinor}.${projPatch}`);
63
+ console.log(`core version: ${coreMajor}.${coreMinor}.${corePatch}`);
62
64
 
63
65
  let newVersion;
64
66
 
65
67
  if (projMajor !== coreMajor || projMinor !== coreMinor) {
66
68
  // Major or minor mismatch: set version to match doc-detective-core major.minor.0
67
69
  newVersion = `${coreMajor}.${coreMinor}.0`;
68
-
69
- // Validate the new version before setting it
70
- if (!semver.valid(newVersion)) {
71
- console.error(`Error: Generated invalid version: ${newVersion}`);
72
- process.exit(1);
73
- }
74
-
75
70
  console.log(`Version mismatch detected. Setting version to: ${newVersion}`);
76
- execCommand(`npm version --no-git-tag-version ${newVersion}`);
77
71
  } else {
78
72
  // Project version is already equal or greater than core version, just bump patch
79
- console.log("Project version is current or ahead. Bumping patch version.");
80
- execCommand("npm version patch --no-git-tag-version");
81
- // Get the new version after bumping
82
- const updatedPackageJson = JSON.parse(
83
- fs.readFileSync(packageJsonPath, "utf8")
73
+ console.log(
74
+ "Project version is current or ahead. Bumping patch version to:",
75
+ newVersion
84
76
  );
85
- newVersion = updatedPackageJson.version;
77
+ newVersion = `${projMajor}.${projMinor}.${projPatch + 1}`;
78
+ }
79
+
80
+ // Validate the new version before setting it
81
+ if (!semver.valid(newVersion)) {
82
+ console.error(`Error: Generated invalid version: ${newVersion}`);
83
+ process.exit(1);
86
84
  }
87
85
 
86
+ execCommand(`npm version --no-git-tag-version ${newVersion}`);
87
+
88
88
  // Commit changes
89
89
  execCommand("git add package.json package-lock.json");
90
- execCommand(
91
- 'git commit -m "update doc-detective-core [skip ci]"'
92
- );
90
+ execCommand('git commit -m "update doc-detective-core [skip ci]"');
93
91
 
94
92
  // Create tag
95
93
  execCommand(`git tag "v${newVersion}"`);
package/src/index.js CHANGED
@@ -1,7 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { runTests, runCoverage } = require("doc-detective-core");
4
- const { setArgs, setConfig, outputResults, setMeta, getVersionData, log } = require("./utils");
3
+ const { runTests } = require("doc-detective-core");
4
+ const {
5
+ setArgs,
6
+ setConfig,
7
+ outputResults,
8
+ setMeta,
9
+ getVersionData,
10
+ log,
11
+ getResolvedTestsFromEnv,
12
+ reportResults,
13
+ } = require("./utils");
5
14
  const { argv } = require("node:process");
6
15
  const path = require("path");
7
16
  const fs = require("fs");
@@ -36,16 +45,28 @@ async function main(argv) {
36
45
  // Set config
37
46
  const config = await setConfig({ configPath: configPath, args: argv });
38
47
 
39
- if (config.logLevel === "debug") {
40
- console.log(`CLI:VERSION INFO:\n${JSON.stringify(getVersionData(), null, 2)}`);
41
- console.log(`CLI:CONFIG:\n${JSON.stringify(config, null, 2)}`);
42
- }
48
+ log(
49
+ `CLI:VERSION INFO:\n${JSON.stringify(getVersionData(), null, 2)}`,
50
+ "debug",
51
+ config
52
+ );
53
+ log(`CLI:CONFIG:\n${JSON.stringify(config, null, 2)}`, "debug", config);
54
+
55
+ // Check for DOC_DETECTIVE_API environment variable
56
+ let api = await getResolvedTestsFromEnv(config);
57
+ let resolvedTests = api?.resolvedTests || null;
58
+ let apiConfig = api?.apiConfig || null;
43
59
 
44
60
  // Run tests
45
61
  const output = config.output;
46
- const results = await runTests(config);
62
+ const results = resolvedTests
63
+ ? await runTests(config, { resolvedTests })
64
+ : await runTests(config);
47
65
 
48
- // Output results
49
- await outputResults(config, output, results, { command: "runTests" });
50
-
51
- }
66
+ if (apiConfig) {
67
+ await reportResults({ apiConfig, results });
68
+ } else {
69
+ // Output results
70
+ await outputResults(config, output, results, { command: "runTests" });
71
+ }
72
+ }
package/src/utils.js CHANGED
@@ -5,6 +5,7 @@ const path = require("path");
5
5
  const fs = require("fs");
6
6
  const { spawn } = require("child_process");
7
7
  const os = require("os");
8
+ const axios = require("axios");
8
9
 
9
10
  exports.setArgs = setArgs;
10
11
  exports.setConfig = setConfig;
@@ -12,6 +13,26 @@ exports.outputResults = outputResults;
12
13
  exports.spawnCommand = spawnCommand;
13
14
  exports.setMeta = setMeta;
14
15
  exports.getVersionData = getVersionData;
16
+ exports.log = log;
17
+ exports.getResolvedTestsFromEnv = getResolvedTestsFromEnv;
18
+ exports.reportResults = reportResults;
19
+
20
+ // Log function that respects logLevel
21
+ function log(message, level = "info", config = {}) {
22
+ const logLevels = ["silent", "error", "warning", "info", "debug"];
23
+ const currentLevel = config.logLevel || "info";
24
+ const currentLevelIndex = logLevels.indexOf(currentLevel);
25
+ const messageLevelIndex = logLevels.indexOf(level);
26
+
27
+ // Only log if the message level is at or above the current log level
28
+ if (currentLevelIndex >= messageLevelIndex && messageLevelIndex > 0) {
29
+ if (level === "error") {
30
+ console.error(message);
31
+ } else {
32
+ console.log(message);
33
+ }
34
+ }
35
+ }
15
36
 
16
37
  // Define args
17
38
  function setArgs(args) {
@@ -50,6 +71,116 @@ function setArgs(args) {
50
71
  return argv;
51
72
  }
52
73
 
74
+ // Get resolved tests from environment variable, if set
75
+ async function getResolvedTestsFromEnv(config = {}) {
76
+ if (!process.env.DOC_DETECTIVE_API) {
77
+ return null;
78
+ }
79
+
80
+ let resolvedTests = null;
81
+ let apiConfig = null;
82
+ try {
83
+ // Parse the environment variable as JSON
84
+ apiConfig = JSON.parse(process.env.DOC_DETECTIVE_API);
85
+
86
+ // Validate the structure: { accountId, url, token, contextIds }
87
+ if (!apiConfig.accountId || !apiConfig.url || !apiConfig.token || !apiConfig.contextIds) {
88
+ log(
89
+ "Invalid DOC_DETECTIVE_API: must contain 'accountId', 'url', 'token', and 'contextIds' properties",
90
+ "error",
91
+ config
92
+ );
93
+ process.exit(1);
94
+ }
95
+
96
+ log(`CLI:Fetching resolved tests from ${apiConfig.url}/resolved-tests`, "debug", config);
97
+
98
+ // Make GET request to the specified URL with token in header
99
+ const response = await axios.get(`${apiConfig.url}/resolved-tests`, {
100
+ headers: {
101
+ "x-runner-token": apiConfig.token,
102
+ },
103
+ });
104
+
105
+ // The response is the resolvedTests
106
+ resolvedTests = response.data;
107
+
108
+ // Validate against resolvedTests_v3 schema
109
+ const validation = validate({
110
+ schemaKey: "resolvedTests_v3",
111
+ object: resolvedTests,
112
+ });
113
+
114
+ if (!validation.valid) {
115
+ log(
116
+ "Invalid resolvedTests from API response. " + validation.errors,
117
+ "error",
118
+ config
119
+ );
120
+ process.exit(1);
121
+ }
122
+
123
+ // Get config from environment variable for merging
124
+ const envConfig = await getConfigFromEnv();
125
+ if (envConfig) {
126
+ // Apply config overrides to resolvedTests.config
127
+ if (resolvedTests.config) {
128
+ resolvedTests.config = { ...resolvedTests.config, ...envConfig };
129
+ } else {
130
+ resolvedTests.config = envConfig;
131
+ }
132
+ }
133
+
134
+ log(
135
+ `CLI:RESOLVED_TESTS:\n${JSON.stringify(resolvedTests, null, 2)}`,
136
+ "debug",
137
+ config
138
+ );
139
+ } catch (error) {
140
+ log(
141
+ `Error fetching resolved tests from DOC_DETECTIVE_API: ${error.message}`,
142
+ "error",
143
+ config
144
+ );
145
+ process.exit(1);
146
+ }
147
+ return { apiConfig, resolvedTests };
148
+ }
149
+
150
+ async function getConfigFromEnv() {
151
+ if (!process.env.DOC_DETECTIVE_CONFIG) {
152
+ return null;
153
+ }
154
+
155
+ let envConfig = null;
156
+ try {
157
+ // Parse the environment variable as JSON
158
+ envConfig = JSON.parse(process.env.DOC_DETECTIVE_CONFIG);
159
+
160
+ // Validate the environment variable config
161
+ const envValidation = validate({
162
+ schemaKey: "config_v3",
163
+ object: envConfig,
164
+ });
165
+
166
+ if (!envValidation.valid) {
167
+ console.error(
168
+ "Invalid config from DOC_DETECTIVE_CONFIG environment variable.",
169
+ envValidation.errors
170
+ );
171
+ process.exit(1);
172
+ }
173
+
174
+ log(`CLI:ENV_CONFIG:\n${JSON.stringify(envConfig, null, 2)}`, "debug", envConfig);
175
+ } catch (error) {
176
+ console.error(
177
+ `Error parsing DOC_DETECTIVE_CONFIG environment variable: ${error.message}`
178
+ );
179
+ process.exit(1);
180
+ }
181
+ return envConfig;
182
+ }
183
+
53
184
  // Override config values based on args and validate the config
54
185
  async function setConfig({ configPath, args }) {
55
186
  if (args.config && !configPath) {
@@ -67,6 +198,13 @@ async function setConfig({ configPath, args }) {
67
198
  }
68
199
  }
69
200
 
201
+ // Check for DOC_DETECTIVE_CONFIG environment variable
202
+ const envConfig = await getConfigFromEnv();
203
+ if (envConfig) {
204
+ // Merge with file config, preferring environment variable config (use raw envConfig, not validated with defaults)
205
+ config = { ...config, ...envConfig };
206
+ }
207
+
70
208
  // Validate config
71
209
  const validation = validate({
72
210
  schemaKey: "config_v3",
@@ -588,6 +726,70 @@ function registerReporter(name, reporterFunction) {
588
726
  // Export the registerReporter function
589
727
  exports.registerReporter = registerReporter;
590
728
 
729
+ async function reportResults({ apiConfig, results }) {
730
+ // Transform results into the required format for the API
731
+ // Extract contexts from the nested structure and format them
732
+ const contexts = [];
733
+
734
+ if (results.specs) {
735
+ results.specs.forEach((spec) => {
736
+ if (spec.tests) {
737
+ spec.tests.forEach((test) => {
738
+ if (test.contexts) {
739
+ test.contexts.forEach((context) => {
740
+ // Extract or generate contextId
741
+ const contextId =
742
+ context.contextId;
743
+
744
+ // Convert result status to lowercase (PASS -> passed, FAIL -> failed, etc.)
745
+ let status;
746
+ if (context.result === "PASS") {
747
+ status = "passed";
748
+ } else if (context.result === "FAIL") {
749
+ status = "failed";
750
+ } else if (context.result === "WARNING") {
751
+ status = "warning";
752
+ } else if (context.result === "SKIPPED") {
753
+ status = "skipped";
754
+ }
755
+ if (!status) {
756
+ log(config, "error", `Unknown context result status for context ID ${contextId}`);
757
+ return;
758
+ }
759
+
760
+ // Build the context payload with the entire context object embedded
761
+ contexts.push({
762
+ contextId: contextId,
763
+ status: status,
764
+ result: context,
765
+ });
766
+ });
767
+ }
768
+ });
769
+ }
770
+ });
771
+ }
772
+
773
+ // POST to the /contexts endpoint
774
+ try {
775
+ const url = `${apiConfig.url}/contexts`;
776
+ const payload = { contexts };
777
+
778
+ console.log(payload);
779
+
780
+ const response = await axios.post(url, payload, {
781
+ headers: {
782
+ "x-runner-token": apiConfig.token,
783
+ },
784
+ });
785
+ console.log("Results reported successfully:", response.data);
786
+ } catch (error) {
787
+ console.error(
788
+ `Error reporting results to ${apiConfig.url}/contexts: ${error.message}`
789
+ );
790
+ }
791
+ }
792
+
591
793
  async function outputResults(config = {}, outputPath, results, options = {}) {
592
794
  // Default to using both built-in reporters if none specified
593
795
  const defaultReporters = ["terminal", "json"];
@@ -653,7 +855,9 @@ async function spawnCommand(cmd, args) {
653
855
  }
654
856
  }
655
857
 
656
- const runCommand = spawn(cmd, args);
858
+ const runCommand = spawn(cmd, args, {
859
+ env: process.env, // Explicitly pass environment variables
860
+ });
657
861
 
658
862
  // Capture stdout
659
863
  let stdout = "";
@@ -0,0 +1,193 @@
1
+ const { createServer } = require("./server");
2
+ const path = require("path");
3
+ const { spawnCommand } = require("../src/utils");
4
+ const assert = require("assert").strict;
5
+ const fs = require("fs");
6
+ const artifactPath = path.resolve(__dirname, "./artifacts");
7
+ const outputFile = path.resolve(`${artifactPath}/resolvedTestsResults.json`);
8
+
9
+ // Create a server with custom options
10
+ const server = createServer({
11
+ port: 8093,
12
+ staticDir: "./test/server/public",
13
+ });
14
+
15
+ // Start the server before tests
16
+ before(async () => {
17
+ try {
18
+ await server.start();
19
+ } catch (error) {
20
+ console.error(`Failed to start test server: ${error.message}`);
21
+ throw error;
22
+ }
23
+ });
24
+
25
+ // Stop the server after tests
26
+ after(async () => {
27
+ try {
28
+ await server.stop();
29
+ } catch (error) {
30
+ console.error(`Failed to stop test server: ${error.message}`);
31
+ }
32
+ });
33
+
34
+ describe("DOC_DETECTIVE_API environment variable", function () {
35
+ // Set indefinite timeout
36
+ this.timeout(0);
37
+
38
+ it("Should fetch and run resolved tests from API", async () => {
39
+ const apiConfig = {
40
+ accountId: "test-account",
41
+ url: "http://localhost:8093/api/resolved-tests",
42
+ token: "test-token-123",
43
+ contextIds: "test-context",
44
+ };
45
+
46
+ // Set environment variable
47
+ const originalEnv = process.env.DOC_DETECTIVE_API;
48
+ process.env.DOC_DETECTIVE_API = JSON.stringify(apiConfig);
49
+
50
+ try {
51
+ const result = await spawnCommand(
52
+ `node ./src/index.js -o ${outputFile}`
53
+ );
54
+
55
+ // Wait until the file is written
56
+ let waitCount = 0;
57
+ while (!fs.existsSync(outputFile) && waitCount < 50) {
58
+ await new Promise((resolve) => setTimeout(resolve, 100));
59
+ waitCount++;
60
+ }
61
+
62
+ if (fs.existsSync(outputFile)) {
63
+ const testResult = require(outputFile);
64
+ console.log(
65
+ "API Result summary:",
66
+ JSON.stringify(testResult.summary, null, 2)
67
+ );
68
+ // Clean up the require cache
69
+ delete require.cache[require.resolve(outputFile)];
70
+ fs.unlinkSync(outputFile);
71
+
72
+ // Check that tests were run
73
+ assert.ok(testResult.summary);
74
+ assert.ok(testResult.specs);
75
+ }
76
+ } finally {
77
+ // Restore original env
78
+ if (originalEnv !== undefined) {
79
+ process.env.DOC_DETECTIVE_API = originalEnv;
80
+ } else {
81
+ delete process.env.DOC_DETECTIVE_API;
82
+ }
83
+ }
84
+ });
85
+
86
+ it("Should reject API config without required fields", async () => {
87
+ const invalidApiConfig = {
88
+ accountId: "test-account",
89
+ // Missing url and token
90
+ };
91
+
92
+ const originalEnv = process.env.DOC_DETECTIVE_API;
93
+ process.env.DOC_DETECTIVE_API = JSON.stringify(invalidApiConfig);
94
+
95
+ try {
96
+ const result = await spawnCommand(
97
+ `node ./src/index.js -o ${outputFile}`
98
+ );
99
+
100
+ // Should exit with non-zero code
101
+ assert.notEqual(result.exitCode, 0);
102
+ } finally {
103
+ // Restore original env
104
+ if (originalEnv !== undefined) {
105
+ process.env.DOC_DETECTIVE_API = originalEnv;
106
+ } else {
107
+ delete process.env.DOC_DETECTIVE_API;
108
+ }
109
+ }
110
+ });
111
+
112
+ it("Should reject unauthorized API requests", async () => {
113
+ const apiConfigBadToken = {
114
+ accountId: "test-account",
115
+ url: "http://localhost:8093/api/resolved-tests",
116
+ token: "wrong-token",
117
+ contextIds: "test-context",
118
+ };
119
+
120
+ const originalEnv = process.env.DOC_DETECTIVE_API;
121
+ process.env.DOC_DETECTIVE_API = JSON.stringify(apiConfigBadToken);
122
+
123
+ try {
124
+ const result = await spawnCommand(
125
+ `node ./src/index.js -o ${outputFile}`
126
+ );
127
+
128
+ // Should exit with non-zero code due to 401 response
129
+ assert.notEqual(result.exitCode, 0);
130
+ } finally {
131
+ // Restore original env
132
+ if (originalEnv !== undefined) {
133
+ process.env.DOC_DETECTIVE_API = originalEnv;
134
+ } else {
135
+ delete process.env.DOC_DETECTIVE_API;
136
+ }
137
+ }
138
+ });
139
+
140
+ it("Should apply config overrides from DOC_DETECTIVE_CONFIG to API-fetched tests", async () => {
141
+ const apiConfig = {
142
+ accountId: "test-account",
143
+ url: "http://localhost:8093/api/resolved-tests",
144
+ token: "test-token-123",
145
+ contextIds: "test-context",
146
+ };
147
+
148
+ const configOverride = {
149
+ logLevel: "debug",
150
+ };
151
+
152
+ const originalApiEnv = process.env.DOC_DETECTIVE_API;
153
+ const originalConfigEnv = process.env.DOC_DETECTIVE_CONFIG;
154
+ process.env.DOC_DETECTIVE_API = JSON.stringify(apiConfig);
155
+ process.env.DOC_DETECTIVE_CONFIG = JSON.stringify(configOverride);
156
+
157
+ try {
158
+ await spawnCommand(
159
+ `node ./src/index.js -o ${outputFile}`
160
+ );
161
+
162
+ // Wait until the file is written
163
+ let waitCount = 0;
164
+ while (!fs.existsSync(outputFile) && waitCount < 50) {
165
+ await new Promise((resolve) => setTimeout(resolve, 100));
166
+ waitCount++;
167
+ }
168
+
169
+ if (fs.existsSync(outputFile)) {
170
+ const testResult = require(outputFile);
171
+ // Clean up the require cache
172
+ delete require.cache[require.resolve(outputFile)];
173
+ fs.unlinkSync(outputFile);
174
+
175
+ // Check that tests were run
176
+ assert.ok(testResult.summary);
177
+ assert.ok(testResult.specs);
178
+ }
179
+ } finally {
180
+ // Restore original env
181
+ if (originalApiEnv !== undefined) {
182
+ process.env.DOC_DETECTIVE_API = originalApiEnv;
183
+ } else {
184
+ delete process.env.DOC_DETECTIVE_API;
185
+ }
186
+ if (originalConfigEnv !== undefined) {
187
+ process.env.DOC_DETECTIVE_CONFIG = originalConfigEnv;
188
+ } else {
189
+ delete process.env.DOC_DETECTIVE_CONFIG;
190
+ }
191
+ }
192
+ });
193
+ });
@@ -54,6 +54,52 @@ function createServer(options = {}) {
54
54
  }
55
55
  });
56
56
 
57
+ // Endpoint for testing DOC_DETECTIVE_API - returns resolved tests
58
+ app.get("/api/resolved-tests", (req, res) => {
59
+ try {
60
+ // Check for x-runner-token header
61
+ const token = req.headers['x-runner-token'];
62
+
63
+ if (!token || token !== 'test-token-123') {
64
+ return res.status(401).json({ error: "Unauthorized" });
65
+ }
66
+
67
+ // Return a valid resolvedTests object
68
+ const resolvedTests = {
69
+ "resolvedTestsId": "api-resolved-tests-id",
70
+ "config": {
71
+ "logLevel": "info"
72
+ },
73
+ "specs": [
74
+ {
75
+ "specId": "api-spec",
76
+ "tests": [
77
+ {
78
+ "testId": "api-test",
79
+ "contexts": [
80
+ {
81
+ "contextId": "api-context",
82
+ "steps": [
83
+ {
84
+ "stepId": "step-1",
85
+ "checkLink": `http://localhost:${port}`
86
+ }
87
+ ]
88
+ }
89
+ ]
90
+ }
91
+ ]
92
+ }
93
+ ]
94
+ };
95
+
96
+ res.json(resolvedTests);
97
+ } catch (error) {
98
+ console.error("Error processing resolved tests request:", error);
99
+ res.status(500).json({ error: "Internal server error" });
100
+ }
101
+ });
102
+
57
103
  return {
58
104
  /**
59
105
  * Start the server
@@ -204,6 +204,59 @@ describe("Util tests", function () {
204
204
  // Clean up
205
205
  fs.unlinkSync(outputResultsPath);
206
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
+ });
207
260
  });
208
261
 
209
262
  // Deeply compares two objects