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.
- package/dev/dev.config.json +9 -0
- package/dev/dev.spec.json +1 -33
- package/package.json +4 -3
- package/reference.png +0 -0
- package/scripts/bump-sync-version-core.js +19 -21
- package/src/index.js +32 -11
- package/src/utils.js +205 -1
- package/test/resolvedTests.test.js +193 -0
- package/test/server/index.js +46 -0
- package/test/utils.test.js +53 -0
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.
|
|
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
|
-
"
|
|
37
|
-
"doc-detective-
|
|
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
|
-
|
|
24
|
-
|
|
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}
|
|
61
|
-
console.log(`core version: ${coreMajor}.${coreMinor}
|
|
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(
|
|
80
|
-
|
|
81
|
-
|
|
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 =
|
|
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
|
|
4
|
-
const {
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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 =
|
|
62
|
+
const results = resolvedTests
|
|
63
|
+
? await runTests(config, { resolvedTests })
|
|
64
|
+
: await runTests(config);
|
|
47
65
|
|
|
48
|
-
|
|
49
|
-
|
|
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
|
+
});
|
package/test/server/index.js
CHANGED
|
@@ -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
|
package/test/utils.test.js
CHANGED
|
@@ -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
|