doc-detective 3.0.0 → 3.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doc-detective",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
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,12 +33,12 @@
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.0.0",
37
- "doc-detective-core": "^3.0.0",
36
+ "doc-detective-common": "^3.0.2",
37
+ "doc-detective-core": "^3.0.4",
38
38
  "yargs": "^17.7.2"
39
39
  },
40
40
  "devDependencies": {
41
41
  "chai": "^5.2.0",
42
- "mocha": "^11.1.0"
42
+ "mocha": "^11.2.2"
43
43
  }
44
44
  }
package/src/utils.js CHANGED
@@ -77,12 +77,14 @@ async function setConfig(config, args, configPath) {
77
77
  }
78
78
  // Override config values
79
79
  if (args.input) {
80
- config.input = args.input;
80
+ config.input = path.resolve(args.input);
81
81
  }
82
82
  if (args.output) {
83
- config.output = args.output;
83
+ config.output = path.resolve(args.output);
84
+ }
85
+ if (args.logLevel) {
86
+ config.logLevel = args.logLevel;
84
87
  }
85
- if (args.logLevel) config.logLevel = args.logLevel;
86
88
  // Resolve paths
87
89
  config = await resolvePaths({
88
90
  config: config,
@@ -0,0 +1,37 @@
1
+ tests:
2
+ - steps:
3
+ - loadVariables: env
4
+ - httpRequest:
5
+ url: http://localhost:8080/api/users
6
+ method: post
7
+ request:
8
+ body:
9
+ name: $USER
10
+ job: $JOB
11
+ response:
12
+ body:
13
+ name: John Doe
14
+ job: Software Engineer
15
+ - httpRequest:
16
+ url: http://localhost:8080/api/users
17
+ method: post
18
+ request:
19
+ body:
20
+ data:
21
+ - first_name: George
22
+ last_name: Bluth
23
+ id: 1
24
+ response:
25
+ body:
26
+ data:
27
+ - first_name: George
28
+ last_name: Bluth
29
+ variables:
30
+ ID: $$response.body.data[0].id
31
+ - httpRequest:
32
+ url: http://localhost:8080/api/$ID
33
+ method: get
34
+ timeout: 1000
35
+ savePath: response.json
36
+ maxVariation: 0
37
+ overwrite: aboveVariation
@@ -20,20 +20,6 @@
20
20
  "action": "checkLink",
21
21
  "url": "https://www.duckduckgo.com"
22
22
  },
23
- {
24
- "action": "httpRequest",
25
- "url": "https://reqres.in/api/users",
26
- "method": "post",
27
- "requestData": {
28
- "name": "morpheus",
29
- "job": "leader"
30
- },
31
- "responseData": {
32
- "name": "morpheus",
33
- "job": "leader"
34
- },
35
- "statusCodes": [200, 201]
36
- },
37
23
  {
38
24
  "action": "goTo",
39
25
  "url": "https://www.google.com"
@@ -1,3 +1,4 @@
1
+ const { createServer } = require("./server");
1
2
  const path = require("path");
2
3
  const { spawnCommand } = require("../src/utils");
3
4
  const assert = require("assert").strict;
@@ -5,6 +6,36 @@ const fs = require("fs");
5
6
  const artifactPath = path.resolve(__dirname, "./artifacts");
6
7
  const outputFile = path.resolve(`${artifactPath}/testResults.json`);
7
8
 
9
+ // Create a server with custom options
10
+ const server = createServer({
11
+ port: 8080,
12
+ staticDir: './test/server/public',
13
+ modifyResponse: (req, body) => {
14
+ // Optional modification of responses
15
+ return { ...body, extraField: 'added by server' };
16
+ }
17
+ });
18
+
19
+ // Start the server before tests
20
+ before(async () => {
21
+ try {
22
+ await server.start();
23
+ } catch (error) {
24
+ console.error(`Failed to start test server: ${error.message}`);
25
+ throw error;
26
+ }
27
+ });
28
+
29
+ // Stop the server after tests
30
+ after(async () => {
31
+ try {
32
+ await server.stop();
33
+ } catch (error) {
34
+ console.error(`Failed to stop test server: ${error.message}`);
35
+ // Don't rethrow here to avoid masking test failures
36
+ }
37
+ });
38
+
8
39
  describe("Run tests successfully", function () {
9
40
  // Set indefinite timeout
10
41
  this.timeout(0);
@@ -0,0 +1,139 @@
1
+ const express = require("express");
2
+ const bodyParser = require("body-parser");
3
+ const path = require("path");
4
+ const fs = require("fs");
5
+
6
+ /**
7
+ * Creates an echo server that can serve static content and echo back API requests
8
+ * @param {Object} options - Configuration options
9
+ * @param {number} [options.port=8080] - Port to run the server on
10
+ * @param {string} [options.staticDir="public"] - Directory to serve static files from
11
+ * @param {Function} [options.modifyResponse] - Function to modify responses before sending
12
+ * @returns {Object} Server object with start and stop methods
13
+ */
14
+ function createServer(options = {}) {
15
+ const {
16
+ port = 8080,
17
+ staticDir = "public",
18
+ modifyResponse = (req, body) => body,
19
+ } = options;
20
+
21
+ const app = express();
22
+ let server = null;
23
+
24
+ // Parse JSON and urlencoded bodies
25
+ app.use(bodyParser.json({ limit: "10mb" }));
26
+ app.use(bodyParser.urlencoded({ extended: true }));
27
+
28
+ // Serve static files if a directory is provided
29
+ if (staticDir && fs.existsSync(staticDir)) {
30
+ app.use(express.static(staticDir));
31
+ }
32
+
33
+ // Echo API endpoint that returns the request body
34
+ app.all("/api/:path", (req, res) => {
35
+ try {
36
+ const requestBody = req.method === "GET" ? req.query : req.body;
37
+ const modifiedResponse = modifyResponse(req, requestBody);
38
+ console.log("Request:", {
39
+ Method: req.method,
40
+ Path: req.path,
41
+ Query: req.query,
42
+ Headers: req.headers,
43
+ Body: req.body,
44
+ });
45
+
46
+ res.set("x-server", "doc-detective-echo-server");
47
+
48
+ console.log("Response:", { Body: modifiedResponse });
49
+
50
+ res.json(modifiedResponse);
51
+ } catch (error) {
52
+ console.error("Error processing request:", error);
53
+ res.status(500).json({ error: "Internal server error" });
54
+ }
55
+ });
56
+
57
+ return {
58
+ /**
59
+ * Start the server
60
+ * @returns {Promise} Promise that resolves with the server address
61
+ */
62
+
63
+ start: () => {
64
+ return new Promise((resolve, reject) => {
65
+ try {
66
+ server = app.listen(port, () => {
67
+ const serverAddress = `http://localhost:${port}`;
68
+ console.log(`Echo server running at ${serverAddress}`);
69
+ resolve(serverAddress);
70
+ });
71
+
72
+ server.on("error", (error) => {
73
+ console.error(`Failed to start server: ${error.message}`);
74
+ reject(error);
75
+ });
76
+ } catch (error) {
77
+ console.error(`Error setting up server: ${error.message}`);
78
+ reject(error);
79
+ }
80
+ });
81
+ },
82
+
83
+ /**
84
+ * Stop the server
85
+ * @returns {Promise} Promise that resolves when server is stopped
86
+ */
87
+ stop: () => {
88
+ return new Promise((resolve) => {
89
+ if (server) {
90
+ server.close((error) => {
91
+ if (error) {
92
+ console.error("Error stopping server:", error);
93
+ reject(error);
94
+ } else {
95
+ console.log("Echo server stopped");
96
+ server = null;
97
+ resolve();
98
+ }
99
+ });
100
+ } else {
101
+ resolve();
102
+ }
103
+ });
104
+ },
105
+
106
+ /**
107
+ * Get the Express app instance
108
+ * @returns {Object} Express app
109
+ */
110
+ getApp: () => app,
111
+ };
112
+ }
113
+
114
+ // Export the function
115
+ module.exports = { createServer };
116
+
117
+ // If this file is run directly, start a server
118
+ if (require.main === module) {
119
+ const server = createServer({
120
+ port: process.env.PORT || 8080,
121
+ staticDir:
122
+ process.env.STATIC_DIR ||
123
+ path.join(process.cwd(), "./test/server/public"),
124
+ });
125
+
126
+ server.start();
127
+
128
+ // Handle graceful shutdown
129
+ const shutdown = () => {
130
+ console.log("Shutting down server...");
131
+ server
132
+ .stop()
133
+ .then(() => process.exit(0))
134
+ .catch(() => process.exit(1));
135
+ };
136
+
137
+ process.on("SIGINT", shutdown);
138
+ process.on("SIGTERM", shutdown);
139
+ }
@@ -0,0 +1,174 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Basic HTML Elements Demo</title>
7
+ </head>
8
+ <body>
9
+ <header>
10
+ <h1>Common HTML Elements</h1>
11
+ <p>This page demonstrates common HTML elements a user might interact with.</p>
12
+ </header>
13
+
14
+ <nav>
15
+ <ul>
16
+ <li><a href="#text-inputs">Text Inputs</a></li>
17
+ <li><a href="#selection-elements">Selection Elements</a></li>
18
+ <li><a href="#buttons">Buttons</a></li>
19
+ <li><a href="#text-elements">Text Elements</a></li>
20
+ <li><a href="#lists">Lists</a></li>
21
+ <li><a href="#tables">Tables</a></li>
22
+ </ul>
23
+ </nav>
24
+
25
+ <main>
26
+ <section id="text-inputs">
27
+ <h2>Text Inputs</h2>
28
+ <form>
29
+ <div>
30
+ <label for="text-input">Text Input:</label>
31
+ <input type="text" id="text-input" name="text-input" placeholder="Enter some text">
32
+ </div>
33
+
34
+ <div>
35
+ <label for="password-input">Password Input:</label>
36
+ <input type="password" id="password-input" name="password-input" placeholder="Enter password">
37
+ </div>
38
+
39
+ <div>
40
+ <label for="email-input">Email Input:</label>
41
+ <input type="email" id="email-input" name="email-input" placeholder="Enter your email">
42
+ </div>
43
+
44
+ <div>
45
+ <label for="textarea">Textarea:</label>
46
+ <textarea id="textarea" name="textarea" rows="4" cols="50" placeholder="Enter longer text here..."></textarea>
47
+ </div>
48
+ </form>
49
+ </section>
50
+
51
+ <section id="selection-elements">
52
+ <h2>Selection Elements</h2>
53
+ <form>
54
+ <div>
55
+ <p>Checkboxes:</p>
56
+ <input type="checkbox" id="checkbox1" name="checkbox1" value="option1">
57
+ <label for="checkbox1">Option 1</label><br>
58
+ <input type="checkbox" id="checkbox2" name="checkbox2" value="option2">
59
+ <label for="checkbox2">Option 2</label><br>
60
+ <input type="checkbox" id="checkbox3" name="checkbox3" value="option3">
61
+ <label for="checkbox3">Option 3</label>
62
+ </div>
63
+
64
+ <div>
65
+ <p>Radio Buttons:</p>
66
+ <input type="radio" id="radio1" name="radio-group" value="option1">
67
+ <label for="radio1">Option 1</label><br>
68
+ <input type="radio" id="radio2" name="radio-group" value="option2">
69
+ <label for="radio2">Option 2</label><br>
70
+ <input type="radio" id="radio3" name="radio-group" value="option3">
71
+ <label for="radio3">Option 3</label>
72
+ </div>
73
+
74
+ <div>
75
+ <label for="select">Dropdown Select:</label>
76
+ <select id="select" name="select">
77
+ <option value="">-- Please choose an option --</option>
78
+ <option value="option1">Option 1</option>
79
+ <option value="option2">Option 2</option>
80
+ <option value="option3">Option 3</option>
81
+ </select>
82
+ </div>
83
+ </form>
84
+ </section>
85
+
86
+ <section id="buttons">
87
+ <h2>Buttons</h2>
88
+ <button type="button">Standard Button</button>
89
+ <input type="button" value="Input Button">
90
+ <input type="submit" value="Submit Button">
91
+ <input type="reset" value="Reset Button">
92
+ </section>
93
+
94
+ <section id="text-elements">
95
+ <h2>Text Elements</h2>
96
+ <h3>This is a heading 3</h3>
97
+ <h4>This is a heading 4</h4>
98
+ <p>This is a paragraph of text. It demonstrates the paragraph element in HTML.</p>
99
+ <p><strong>Bold text</strong> and <em>italic text</em> are created using the strong and em elements.</p>
100
+ <p>This is a <a href="https://example.com">hyperlink</a> to example.com.</p>
101
+ <blockquote>
102
+ This is a blockquote. It's typically used for quoting content from another source.
103
+ </blockquote>
104
+ <pre>This is preformatted text.
105
+ It preserves both spaces
106
+ and line breaks.</pre>
107
+ <code>This is inline code.</code>
108
+ </section>
109
+
110
+ <section id="lists">
111
+ <h2>Lists</h2>
112
+ <h3>Unordered List</h3>
113
+ <ul>
114
+ <li>Item 1</li>
115
+ <li>Item 2</li>
116
+ <li>Item 3</li>
117
+ </ul>
118
+
119
+ <h3>Ordered List</h3>
120
+ <ol>
121
+ <li>First item</li>
122
+ <li>Second item</li>
123
+ <li>Third item</li>
124
+ </ol>
125
+
126
+ <h3>Definition List</h3>
127
+ <dl>
128
+ <dt>Term 1</dt>
129
+ <dd>Definition for Term 1</dd>
130
+ <dt>Term 2</dt>
131
+ <dd>Definition for Term 2</dd>
132
+ </dl>
133
+ </section>
134
+
135
+ <section id="tables">
136
+ <h2>Tables</h2>
137
+ <table border="1">
138
+ <caption>Simple Data Table</caption>
139
+ <thead>
140
+ <tr>
141
+ <th>Name</th>
142
+ <th>Age</th>
143
+ <th>Country</th>
144
+ </tr>
145
+ </thead>
146
+ <tbody>
147
+ <tr>
148
+ <td>John Doe</td>
149
+ <td>25</td>
150
+ <td>USA</td>
151
+ </tr>
152
+ <tr>
153
+ <td>Jane Smith</td>
154
+ <td>30</td>
155
+ <td>Canada</td>
156
+ </tr>
157
+ <tr>
158
+ <td>Bob Johnson</td>
159
+ <td>45</td>
160
+ <td>UK</td>
161
+ </tr>
162
+ </tbody>
163
+ </table>
164
+ </section>
165
+ </main>
166
+
167
+ <footer>
168
+ <p>&copy; 2025 Basic HTML Elements Demo</p>
169
+ <address>
170
+ Contact: <a href="mailto:example@example.com">example@example.com</a>
171
+ </address>
172
+ </footer>
173
+ </body>
174
+ </html>
@@ -1,55 +0,0 @@
1
- {
2
- "id": "http tests",
3
- "description": "This is a test collection",
4
- "tests": [
5
- {
6
- "id": "env variables test",
7
- "description": "These tests will show off how using env files to set variables works in Doc Detective.",
8
- "steps": [
9
- {
10
- "action": "setVariables",
11
- "path": "env"
12
- },
13
- {
14
- "action": "httpRequest",
15
- "url": "https://reqres.in/api/users",
16
- "method": "post",
17
- "requestData": {
18
- "auth": "$SECRET",
19
- "name": "$USER",
20
- "job": "$JOB"
21
- },
22
- "responseData": {
23
- "name": "John Doe",
24
- "job": "Software Engineer"
25
- },
26
- "statusCodes": [200, 201]
27
- },
28
- {
29
- "action": "httpRequest",
30
- "url": "https://reqres.in/api/users",
31
- "method": "get",
32
- "envsFromResponseData": [
33
- {
34
- "name": "ID",
35
- "jqFilter": ".data[0].id"
36
- }
37
- ],
38
- "statusCodes": [200, 201]
39
- },
40
- {
41
- "action": "httpRequest",
42
- "url": "https://reqres.in/api/users/$ID",
43
- "method": "get",
44
- "responseData": {
45
- "data": {
46
- "first_name": "George",
47
- "last_name": "Bluth"
48
- }
49
- },
50
- "statusCodes": [200, 201]
51
- }
52
- ]
53
- }
54
- ]
55
- }