@apiquest/fracture 1.0.2 → 1.0.4
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/README.md +119 -0
- package/bin/cli.js +2 -2
- package/dist/CollectionRunner.js +3 -3
- package/dist/ScriptEngine.js +4 -4
- package/dist/cli/plugin-commands.d.ts.map +1 -1
- package/dist/cli/plugin-commands.js +2 -1
- package/dist/cli/plugin-commands.js.map +1 -1
- package/package.json +55 -50
- package/src/CollectionAnalyzer.ts +102 -102
- package/src/CollectionRunner.ts +1423 -1423
- package/src/CollectionRunner.types.ts +9 -9
- package/src/CollectionValidator.ts +289 -289
- package/src/ConsoleReporter.ts +143 -143
- package/src/CookieJar.ts +258 -258
- package/src/DagScheduler.ts +439 -439
- package/src/Logger.ts +85 -85
- package/src/PluginLoader.ts +126 -126
- package/src/PluginManager.ts +208 -208
- package/src/PluginResolver.ts +154 -154
- package/src/QuestAPI.ts +764 -764
- package/src/QuestAPI.types.ts +33 -33
- package/src/QuestTestAPI.ts +164 -164
- package/src/RequestFilter.ts +224 -224
- package/src/ScriptEngine.ts +219 -219
- package/src/ScriptValidator.ts +428 -428
- package/src/TaskGraph.ts +598 -598
- package/src/TestCounter.ts +109 -109
- package/src/VariableResolver.ts +114 -114
- package/src/cli/index.ts +480 -480
- package/src/cli/plugin-commands.ts +342 -341
- package/src/cli/plugin-discovery.ts +44 -44
- package/src/index.ts +24 -24
- package/src/utils.ts +52 -52
- package/tsconfig.json +20 -20
- package/tsconfig.test.json +5 -5
- package/vitest.config.ts +22 -22
- package/dist/ExecutionTree.d.ts +0 -77
- package/dist/ExecutionTree.d.ts.map +0 -1
- package/dist/ExecutionTree.js +0 -265
- package/dist/ExecutionTree.js.map +0 -1
- package/dist/fracture/src/CollectionAnalyzer.d.ts +0 -17
- package/dist/fracture/src/CollectionAnalyzer.d.ts.map +0 -1
- package/dist/fracture/src/CollectionAnalyzer.js +0 -70
- package/dist/fracture/src/CollectionAnalyzer.js.map +0 -1
- package/dist/fracture/src/CollectionRunner.d.ts +0 -39
- package/dist/fracture/src/CollectionRunner.d.ts.map +0 -1
- package/dist/fracture/src/CollectionRunner.js +0 -802
- package/dist/fracture/src/CollectionRunner.js.map +0 -1
- package/dist/fracture/src/CollectionRunner.types.d.ts +0 -8
- package/dist/fracture/src/CollectionRunner.types.d.ts.map +0 -1
- package/dist/fracture/src/CollectionRunner.types.js +0 -2
- package/dist/fracture/src/CollectionRunner.types.js.map +0 -1
- package/dist/fracture/src/CollectionValidator.d.ts +0 -14
- package/dist/fracture/src/CollectionValidator.d.ts.map +0 -1
- package/dist/fracture/src/CollectionValidator.js +0 -145
- package/dist/fracture/src/CollectionValidator.js.map +0 -1
- package/dist/fracture/src/ConsoleReporter.d.ts +0 -24
- package/dist/fracture/src/ConsoleReporter.d.ts.map +0 -1
- package/dist/fracture/src/ConsoleReporter.js +0 -123
- package/dist/fracture/src/ConsoleReporter.js.map +0 -1
- package/dist/fracture/src/CookieJar.d.ts +0 -70
- package/dist/fracture/src/CookieJar.d.ts.map +0 -1
- package/dist/fracture/src/CookieJar.js +0 -233
- package/dist/fracture/src/CookieJar.js.map +0 -1
- package/dist/fracture/src/ExecutionTree.d.ts +0 -77
- package/dist/fracture/src/ExecutionTree.d.ts.map +0 -1
- package/dist/fracture/src/ExecutionTree.js +0 -258
- package/dist/fracture/src/ExecutionTree.js.map +0 -1
- package/dist/fracture/src/Logger.d.ts +0 -25
- package/dist/fracture/src/Logger.d.ts.map +0 -1
- package/dist/fracture/src/Logger.js +0 -78
- package/dist/fracture/src/Logger.js.map +0 -1
- package/dist/fracture/src/PluginLoader.d.ts +0 -23
- package/dist/fracture/src/PluginLoader.d.ts.map +0 -1
- package/dist/fracture/src/PluginLoader.js +0 -102
- package/dist/fracture/src/PluginLoader.js.map +0 -1
- package/dist/fracture/src/PluginManager.d.ts +0 -64
- package/dist/fracture/src/PluginManager.d.ts.map +0 -1
- package/dist/fracture/src/PluginManager.js +0 -162
- package/dist/fracture/src/PluginManager.js.map +0 -1
- package/dist/fracture/src/PluginResolver.d.ts +0 -35
- package/dist/fracture/src/PluginResolver.d.ts.map +0 -1
- package/dist/fracture/src/PluginResolver.js +0 -128
- package/dist/fracture/src/PluginResolver.js.map +0 -1
- package/dist/fracture/src/QuestAPI.d.ts +0 -9
- package/dist/fracture/src/QuestAPI.d.ts.map +0 -1
- package/dist/fracture/src/QuestAPI.js +0 -679
- package/dist/fracture/src/QuestAPI.js.map +0 -1
- package/dist/fracture/src/QuestAPI.types.d.ts +0 -35
- package/dist/fracture/src/QuestAPI.types.d.ts.map +0 -1
- package/dist/fracture/src/QuestAPI.types.js +0 -3
- package/dist/fracture/src/QuestAPI.types.js.map +0 -1
- package/dist/fracture/src/QuestTestAPI.d.ts +0 -12
- package/dist/fracture/src/QuestTestAPI.d.ts.map +0 -1
- package/dist/fracture/src/QuestTestAPI.js +0 -133
- package/dist/fracture/src/QuestTestAPI.js.map +0 -1
- package/dist/fracture/src/ScriptEngine.d.ts +0 -21
- package/dist/fracture/src/ScriptEngine.d.ts.map +0 -1
- package/dist/fracture/src/ScriptEngine.js +0 -183
- package/dist/fracture/src/ScriptEngine.js.map +0 -1
- package/dist/fracture/src/ScriptValidator.d.ts +0 -68
- package/dist/fracture/src/ScriptValidator.d.ts.map +0 -1
- package/dist/fracture/src/ScriptValidator.js +0 -351
- package/dist/fracture/src/ScriptValidator.js.map +0 -1
- package/dist/fracture/src/TestCounter.d.ts +0 -18
- package/dist/fracture/src/TestCounter.d.ts.map +0 -1
- package/dist/fracture/src/TestCounter.js +0 -82
- package/dist/fracture/src/TestCounter.js.map +0 -1
- package/dist/fracture/src/VariableResolver.d.ts +0 -20
- package/dist/fracture/src/VariableResolver.d.ts.map +0 -1
- package/dist/fracture/src/VariableResolver.js +0 -100
- package/dist/fracture/src/VariableResolver.js.map +0 -1
- package/dist/fracture/src/cli/index.d.ts +0 -3
- package/dist/fracture/src/cli/index.d.ts.map +0 -1
- package/dist/fracture/src/cli/index.js +0 -347
- package/dist/fracture/src/cli/index.js.map +0 -1
- package/dist/fracture/src/cli/plugin-commands.d.ts +0 -6
- package/dist/fracture/src/cli/plugin-commands.d.ts.map +0 -1
- package/dist/fracture/src/cli/plugin-commands.js +0 -263
- package/dist/fracture/src/cli/plugin-commands.js.map +0 -1
- package/dist/fracture/src/cli/plugin-discovery.d.ts +0 -11
- package/dist/fracture/src/cli/plugin-discovery.d.ts.map +0 -1
- package/dist/fracture/src/cli/plugin-discovery.js +0 -64
- package/dist/fracture/src/cli/plugin-discovery.js.map +0 -1
- package/dist/fracture/src/index.d.ts +0 -13
- package/dist/fracture/src/index.d.ts.map +0 -1
- package/dist/fracture/src/index.js +0 -17
- package/dist/fracture/src/index.js.map +0 -1
- package/dist/fracture/src/utils.d.ts +0 -28
- package/dist/fracture/src/utils.d.ts.map +0 -1
- package/dist/fracture/src/utils.js +0 -48
- package/dist/fracture/src/utils.js.map +0 -1
- package/dist/plugin-auth/src/apikey-auth.d.ts +0 -3
- package/dist/plugin-auth/src/apikey-auth.d.ts.map +0 -1
- package/dist/plugin-auth/src/apikey-auth.js +0 -73
- package/dist/plugin-auth/src/apikey-auth.js.map +0 -1
- package/dist/plugin-auth/src/basic-auth.d.ts +0 -3
- package/dist/plugin-auth/src/basic-auth.d.ts.map +0 -1
- package/dist/plugin-auth/src/basic-auth.js +0 -61
- package/dist/plugin-auth/src/basic-auth.js.map +0 -1
- package/dist/plugin-auth/src/bearer-auth.d.ts +0 -3
- package/dist/plugin-auth/src/bearer-auth.d.ts.map +0 -1
- package/dist/plugin-auth/src/bearer-auth.js +0 -49
- package/dist/plugin-auth/src/bearer-auth.js.map +0 -1
- package/dist/plugin-auth/src/helpers.d.ts +0 -3
- package/dist/plugin-auth/src/helpers.d.ts.map +0 -1
- package/dist/plugin-auth/src/helpers.js +0 -8
- package/dist/plugin-auth/src/helpers.js.map +0 -1
- package/dist/plugin-auth/src/index.d.ts +0 -10
- package/dist/plugin-auth/src/index.d.ts.map +0 -1
- package/dist/plugin-auth/src/index.js +0 -25
- package/dist/plugin-auth/src/index.js.map +0 -1
- package/dist/plugin-auth/src/oauth2-auth.d.ts +0 -35
- package/dist/plugin-auth/src/oauth2-auth.d.ts.map +0 -1
- package/dist/plugin-auth/src/oauth2-auth.js +0 -266
- package/dist/plugin-auth/src/oauth2-auth.js.map +0 -1
- package/dist/plugin-http/src/index.d.ts +0 -4
- package/dist/plugin-http/src/index.d.ts.map +0 -1
- package/dist/plugin-http/src/index.js +0 -266
- package/dist/plugin-http/src/index.js.map +0 -1
- package/dist/plugin-vault-file/src/index.d.ts +0 -67
- package/dist/plugin-vault-file/src/index.d.ts.map +0 -1
- package/dist/plugin-vault-file/src/index.js +0 -171
- package/dist/plugin-vault-file/src/index.js.map +0 -1
- package/dist/types.d.ts +0 -374
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -13
- package/dist/types.js.map +0 -1
package/src/TestCounter.ts
CHANGED
|
@@ -1,109 +1,109 @@
|
|
|
1
|
-
import type { Collection, CollectionItem, Request, Folder } from '@apiquest/types';
|
|
2
|
-
import { ScriptValidator } from './ScriptValidator.js';
|
|
3
|
-
import type { PluginManager } from './PluginManager.js';
|
|
4
|
-
import { Logger } from './Logger.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Counts total expected tests in a collection for deterministic test reporting
|
|
8
|
-
*/
|
|
9
|
-
export class TestCounter {
|
|
10
|
-
private logger: Logger;
|
|
11
|
-
|
|
12
|
-
constructor(
|
|
13
|
-
private readonly pluginManager: PluginManager,
|
|
14
|
-
baseLogger?: Logger
|
|
15
|
-
) {
|
|
16
|
-
this.logger = baseLogger?.createLogger('TestCounter') ?? new Logger('TestCounter');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Count total expected tests in collection
|
|
21
|
-
* - Counts quest.test() calls in all scripts
|
|
22
|
-
* - Multiplies by iteration count
|
|
23
|
-
* - Returns -1 if collection has dynamic plugin events (can't determine count)
|
|
24
|
-
* @returns Total expected test count, or -1 if dynamic
|
|
25
|
-
*/
|
|
26
|
-
countTests(collection: Collection): number {
|
|
27
|
-
let totalTests = 0;
|
|
28
|
-
let hasDynamicTests = false;
|
|
29
|
-
|
|
30
|
-
this.logger.debug(`Counting tests for collection: ${collection.info.name}`);
|
|
31
|
-
|
|
32
|
-
// Note: collectionPre/Post and folderPre/Post scripts CANNOT have tests (validation catches this)
|
|
33
|
-
// Only preRequestScript and postRequestScript can have tests
|
|
34
|
-
|
|
35
|
-
// Helper to walk tree and count tests for each REQUEST with inherited scripts
|
|
36
|
-
const countRequestTests = (item: CollectionItem, inheritedPreRequest: string[], inheritedPostRequest: string[]): void => {
|
|
37
|
-
if (item.type === 'folder') {
|
|
38
|
-
const folder = item;
|
|
39
|
-
|
|
40
|
-
// Build inherited script chain (scripts STACK, not override)
|
|
41
|
-
const newInheritedPre = (folder.preRequestScript !== null && folder.preRequestScript !== undefined && folder.preRequestScript.length > 0)
|
|
42
|
-
? [...inheritedPreRequest, folder.preRequestScript]
|
|
43
|
-
: inheritedPreRequest;
|
|
44
|
-
const newInheritedPost = (folder.postRequestScript !== null && folder.postRequestScript !== undefined && folder.postRequestScript.length > 0)
|
|
45
|
-
? [...inheritedPostRequest, folder.postRequestScript]
|
|
46
|
-
: inheritedPostRequest;
|
|
47
|
-
|
|
48
|
-
// Recursively process folder contents
|
|
49
|
-
for (const child of folder.items) {
|
|
50
|
-
countRequestTests(child, newInheritedPre, newInheritedPost);
|
|
51
|
-
}
|
|
52
|
-
} else {
|
|
53
|
-
// Request - count ALL scripts in execution chain for THIS request
|
|
54
|
-
const request = item;
|
|
55
|
-
|
|
56
|
-
// Inherited postRequestScripts (collection and all ancestor folders) - they STACK
|
|
57
|
-
for (const script of inheritedPostRequest) {
|
|
58
|
-
totalTests += ScriptValidator.countTests(script);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Request-level postRequestScript (this is where tests are!)
|
|
62
|
-
if (request.postRequestScript !== null && request.postRequestScript !== undefined && request.postRequestScript.length > 0) {
|
|
63
|
-
totalTests += ScriptValidator.countTests(request.postRequestScript);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Plugin event scripts
|
|
67
|
-
if (request.data.scripts !== null && request.data.scripts !== undefined && Array.isArray(request.data.scripts)) {
|
|
68
|
-
const protocolPlugin = this.pluginManager.getPlugin(collection.protocol);
|
|
69
|
-
if (protocolPlugin?.events !== null && protocolPlugin?.events !== undefined) {
|
|
70
|
-
for (const script of request.data.scripts) {
|
|
71
|
-
const eventDef = protocolPlugin.events.find(e => e.name === script.event);
|
|
72
|
-
if (eventDef?.canHaveTests === true) {
|
|
73
|
-
const expectedMessages = ScriptValidator.extractExpectedMessages(
|
|
74
|
-
(request.preRequestScript !== null && request.preRequestScript !== undefined && request.preRequestScript.length > 0) ? request.preRequestScript : ''
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
if (expectedMessages !== null) {
|
|
78
|
-
const testsPerEvent = ScriptValidator.countTests(script.script);
|
|
79
|
-
totalTests += testsPerEvent * expectedMessages;
|
|
80
|
-
} else {
|
|
81
|
-
hasDynamicTests = true;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
// Process all items with collection-level inherited scripts
|
|
91
|
-
const collectionPre = (collection.preRequestScript !== null && collection.preRequestScript !== undefined && collection.preRequestScript.length > 0) ? [collection.preRequestScript] : [];
|
|
92
|
-
const collectionPost = (collection.postRequestScript !== null && collection.postRequestScript !== undefined && collection.postRequestScript.length > 0) ? [collection.postRequestScript] : [];
|
|
93
|
-
|
|
94
|
-
for (const item of collection.items) {
|
|
95
|
-
countRequestTests(item, collectionPre, collectionPost);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Multiply by iteration count
|
|
99
|
-
const iterationCount = (collection.testData?.length !== null && collection.testData?.length !== undefined && collection.testData.length > 0) ? collection.testData.length : 1;
|
|
100
|
-
totalTests *= iterationCount;
|
|
101
|
-
|
|
102
|
-
const result = hasDynamicTests ? -1 : totalTests;
|
|
103
|
-
if (hasDynamicTests) {
|
|
104
|
-
this.logger.debug('Dynamic test count detected; returning -1');
|
|
105
|
-
}
|
|
106
|
-
this.logger.debug(`Expected test count resolved: ${result}`);
|
|
107
|
-
return result;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
1
|
+
import type { Collection, CollectionItem, Request, Folder } from '@apiquest/types';
|
|
2
|
+
import { ScriptValidator } from './ScriptValidator.js';
|
|
3
|
+
import type { PluginManager } from './PluginManager.js';
|
|
4
|
+
import { Logger } from './Logger.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Counts total expected tests in a collection for deterministic test reporting
|
|
8
|
+
*/
|
|
9
|
+
export class TestCounter {
|
|
10
|
+
private logger: Logger;
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
private readonly pluginManager: PluginManager,
|
|
14
|
+
baseLogger?: Logger
|
|
15
|
+
) {
|
|
16
|
+
this.logger = baseLogger?.createLogger('TestCounter') ?? new Logger('TestCounter');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Count total expected tests in collection
|
|
21
|
+
* - Counts quest.test() calls in all scripts
|
|
22
|
+
* - Multiplies by iteration count
|
|
23
|
+
* - Returns -1 if collection has dynamic plugin events (can't determine count)
|
|
24
|
+
* @returns Total expected test count, or -1 if dynamic
|
|
25
|
+
*/
|
|
26
|
+
countTests(collection: Collection): number {
|
|
27
|
+
let totalTests = 0;
|
|
28
|
+
let hasDynamicTests = false;
|
|
29
|
+
|
|
30
|
+
this.logger.debug(`Counting tests for collection: ${collection.info.name}`);
|
|
31
|
+
|
|
32
|
+
// Note: collectionPre/Post and folderPre/Post scripts CANNOT have tests (validation catches this)
|
|
33
|
+
// Only preRequestScript and postRequestScript can have tests
|
|
34
|
+
|
|
35
|
+
// Helper to walk tree and count tests for each REQUEST with inherited scripts
|
|
36
|
+
const countRequestTests = (item: CollectionItem, inheritedPreRequest: string[], inheritedPostRequest: string[]): void => {
|
|
37
|
+
if (item.type === 'folder') {
|
|
38
|
+
const folder = item;
|
|
39
|
+
|
|
40
|
+
// Build inherited script chain (scripts STACK, not override)
|
|
41
|
+
const newInheritedPre = (folder.preRequestScript !== null && folder.preRequestScript !== undefined && folder.preRequestScript.length > 0)
|
|
42
|
+
? [...inheritedPreRequest, folder.preRequestScript]
|
|
43
|
+
: inheritedPreRequest;
|
|
44
|
+
const newInheritedPost = (folder.postRequestScript !== null && folder.postRequestScript !== undefined && folder.postRequestScript.length > 0)
|
|
45
|
+
? [...inheritedPostRequest, folder.postRequestScript]
|
|
46
|
+
: inheritedPostRequest;
|
|
47
|
+
|
|
48
|
+
// Recursively process folder contents
|
|
49
|
+
for (const child of folder.items) {
|
|
50
|
+
countRequestTests(child, newInheritedPre, newInheritedPost);
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
// Request - count ALL scripts in execution chain for THIS request
|
|
54
|
+
const request = item;
|
|
55
|
+
|
|
56
|
+
// Inherited postRequestScripts (collection and all ancestor folders) - they STACK
|
|
57
|
+
for (const script of inheritedPostRequest) {
|
|
58
|
+
totalTests += ScriptValidator.countTests(script);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Request-level postRequestScript (this is where tests are!)
|
|
62
|
+
if (request.postRequestScript !== null && request.postRequestScript !== undefined && request.postRequestScript.length > 0) {
|
|
63
|
+
totalTests += ScriptValidator.countTests(request.postRequestScript);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Plugin event scripts
|
|
67
|
+
if (request.data.scripts !== null && request.data.scripts !== undefined && Array.isArray(request.data.scripts)) {
|
|
68
|
+
const protocolPlugin = this.pluginManager.getPlugin(collection.protocol);
|
|
69
|
+
if (protocolPlugin?.events !== null && protocolPlugin?.events !== undefined) {
|
|
70
|
+
for (const script of request.data.scripts) {
|
|
71
|
+
const eventDef = protocolPlugin.events.find(e => e.name === script.event);
|
|
72
|
+
if (eventDef?.canHaveTests === true) {
|
|
73
|
+
const expectedMessages = ScriptValidator.extractExpectedMessages(
|
|
74
|
+
(request.preRequestScript !== null && request.preRequestScript !== undefined && request.preRequestScript.length > 0) ? request.preRequestScript : ''
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
if (expectedMessages !== null) {
|
|
78
|
+
const testsPerEvent = ScriptValidator.countTests(script.script);
|
|
79
|
+
totalTests += testsPerEvent * expectedMessages;
|
|
80
|
+
} else {
|
|
81
|
+
hasDynamicTests = true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Process all items with collection-level inherited scripts
|
|
91
|
+
const collectionPre = (collection.preRequestScript !== null && collection.preRequestScript !== undefined && collection.preRequestScript.length > 0) ? [collection.preRequestScript] : [];
|
|
92
|
+
const collectionPost = (collection.postRequestScript !== null && collection.postRequestScript !== undefined && collection.postRequestScript.length > 0) ? [collection.postRequestScript] : [];
|
|
93
|
+
|
|
94
|
+
for (const item of collection.items) {
|
|
95
|
+
countRequestTests(item, collectionPre, collectionPost);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Multiply by iteration count
|
|
99
|
+
const iterationCount = (collection.testData?.length !== null && collection.testData?.length !== undefined && collection.testData.length > 0) ? collection.testData.length : 1;
|
|
100
|
+
totalTests *= iterationCount;
|
|
101
|
+
|
|
102
|
+
const result = hasDynamicTests ? -1 : totalTests;
|
|
103
|
+
if (hasDynamicTests) {
|
|
104
|
+
this.logger.debug('Dynamic test count detected; returning -1');
|
|
105
|
+
}
|
|
106
|
+
this.logger.debug(`Expected test count resolved: ${result}`);
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
}
|
package/src/VariableResolver.ts
CHANGED
|
@@ -1,114 +1,114 @@
|
|
|
1
|
-
import type { ExecutionContext, IterationData } from '@apiquest/types';
|
|
2
|
-
import { extractValue, isNullOrEmpty } from './utils.js';
|
|
3
|
-
import { Logger } from './Logger.js';
|
|
4
|
-
|
|
5
|
-
export class VariableResolver {
|
|
6
|
-
private logger: Logger;
|
|
7
|
-
|
|
8
|
-
constructor(baseLogger?: Logger) {
|
|
9
|
-
this.logger = baseLogger?.createLogger('VariableResolver') ?? new Logger('VariableResolver');
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Resolve {{variables}} in a template string
|
|
14
|
-
* Priority: iteration data > local > collection > environment > global
|
|
15
|
-
*/
|
|
16
|
-
resolve(template: string, context: ExecutionContext): string {
|
|
17
|
-
if (isNullOrEmpty(template) || typeof template !== 'string') {
|
|
18
|
-
return template;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Find all {{variable}} patterns
|
|
22
|
-
const variableCount = (template.match(/\{\{([^}]+)\}\}/g) ?? []).length;
|
|
23
|
-
if (variableCount > 0) {
|
|
24
|
-
this.logger.trace(`Resolving ${variableCount} variable(s) in template`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return template.replace(/\{\{([^}]+)\}\}/g, (match: string, varName: string) => {
|
|
28
|
-
const trimmedName = varName.trim();
|
|
29
|
-
const value = this.getVariable(trimmedName, context);
|
|
30
|
-
|
|
31
|
-
if (value !== null && value !== undefined) {
|
|
32
|
-
this.logger.trace(`Resolved {{${trimmedName}}} -> ${typeof value === 'string' && value.length > 50 ? value.substring(0, 50) + '...' : value}`);
|
|
33
|
-
return String(value);
|
|
34
|
-
} else {
|
|
35
|
-
this.logger.trace(`Variable {{${trimmedName}}} not found, keeping placeholder`);
|
|
36
|
-
return match;
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Resolve all values in an object (recursively)
|
|
43
|
-
*/
|
|
44
|
-
resolveAll(obj: unknown, context: ExecutionContext): unknown {
|
|
45
|
-
if (obj === null || obj === undefined) {
|
|
46
|
-
return obj;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (typeof obj === 'string') {
|
|
50
|
-
return this.resolve(obj, context);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (Array.isArray(obj)) {
|
|
54
|
-
return obj.map(item => this.resolveAll(item, context));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (typeof obj === 'object') {
|
|
58
|
-
const resolved: Record<string, unknown> = {};
|
|
59
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
60
|
-
resolved[key] = this.resolveAll(value, context);
|
|
61
|
-
}
|
|
62
|
-
return resolved;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return obj;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Get variable value with cascading priority
|
|
70
|
-
* Priority: iteration data > scope stack (innermost to outermost) > collection > environment > global
|
|
71
|
-
*/
|
|
72
|
-
private getVariable(name: string, context: ExecutionContext): unknown {
|
|
73
|
-
// 1. Iteration data (highest priority)
|
|
74
|
-
const currentIterationData = context.iterationData?.[context.iterationCurrent - 1];
|
|
75
|
-
if (currentIterationData !== null && currentIterationData !== undefined && name in currentIterationData) {
|
|
76
|
-
this.logger.trace(`Variable '${name}' found in iteration data`);
|
|
77
|
-
return currentIterationData[name];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// 2. Scope stack (hierarchical scope variables - search from innermost to outermost)
|
|
81
|
-
// This represents quest.scope.variables which flows through the script inheritance chain
|
|
82
|
-
if (context.scopeStack !== null && context.scopeStack !== undefined && context.scopeStack.length > 0) {
|
|
83
|
-
// Search from top of stack (most specific) to bottom (least specific)
|
|
84
|
-
for (let i = context.scopeStack.length - 1; i >= 0; i--) {
|
|
85
|
-
const frame = context.scopeStack[i];
|
|
86
|
-
if (name in frame.vars) {
|
|
87
|
-
this.logger.trace(`Variable '${name}' found in scope stack (frame ${context.scopeStack.length - 1 - i})`);
|
|
88
|
-
return frame.vars[name];
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// 3. Collection variables
|
|
94
|
-
if (name in context.collectionVariables) {
|
|
95
|
-
this.logger.trace(`Variable '${name}' found in collection variables`);
|
|
96
|
-
return extractValue(context.collectionVariables[name]);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// 4. Environment variables
|
|
100
|
-
if (context.environment !== null && context.environment !== undefined && name in context.environment.variables) {
|
|
101
|
-
this.logger.trace(`Variable '${name}' found in environment variables`);
|
|
102
|
-
return extractValue(context.environment.variables[name]);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// 5. Global variables (lowest priority)
|
|
106
|
-
if (name in context.globalVariables) {
|
|
107
|
-
this.logger.trace(`Variable '${name}' found in global variables`);
|
|
108
|
-
return extractValue(context.globalVariables[name]);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
this.logger.trace(`Variable '${name}' not found in any scope`);
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
1
|
+
import type { ExecutionContext, IterationData } from '@apiquest/types';
|
|
2
|
+
import { extractValue, isNullOrEmpty } from './utils.js';
|
|
3
|
+
import { Logger } from './Logger.js';
|
|
4
|
+
|
|
5
|
+
export class VariableResolver {
|
|
6
|
+
private logger: Logger;
|
|
7
|
+
|
|
8
|
+
constructor(baseLogger?: Logger) {
|
|
9
|
+
this.logger = baseLogger?.createLogger('VariableResolver') ?? new Logger('VariableResolver');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolve {{variables}} in a template string
|
|
14
|
+
* Priority: iteration data > local > collection > environment > global
|
|
15
|
+
*/
|
|
16
|
+
resolve(template: string, context: ExecutionContext): string {
|
|
17
|
+
if (isNullOrEmpty(template) || typeof template !== 'string') {
|
|
18
|
+
return template;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Find all {{variable}} patterns
|
|
22
|
+
const variableCount = (template.match(/\{\{([^}]+)\}\}/g) ?? []).length;
|
|
23
|
+
if (variableCount > 0) {
|
|
24
|
+
this.logger.trace(`Resolving ${variableCount} variable(s) in template`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return template.replace(/\{\{([^}]+)\}\}/g, (match: string, varName: string) => {
|
|
28
|
+
const trimmedName = varName.trim();
|
|
29
|
+
const value = this.getVariable(trimmedName, context);
|
|
30
|
+
|
|
31
|
+
if (value !== null && value !== undefined) {
|
|
32
|
+
this.logger.trace(`Resolved {{${trimmedName}}} -> ${typeof value === 'string' && value.length > 50 ? value.substring(0, 50) + '...' : value}`);
|
|
33
|
+
return String(value);
|
|
34
|
+
} else {
|
|
35
|
+
this.logger.trace(`Variable {{${trimmedName}}} not found, keeping placeholder`);
|
|
36
|
+
return match;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Resolve all values in an object (recursively)
|
|
43
|
+
*/
|
|
44
|
+
resolveAll(obj: unknown, context: ExecutionContext): unknown {
|
|
45
|
+
if (obj === null || obj === undefined) {
|
|
46
|
+
return obj;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (typeof obj === 'string') {
|
|
50
|
+
return this.resolve(obj, context);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (Array.isArray(obj)) {
|
|
54
|
+
return obj.map(item => this.resolveAll(item, context));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (typeof obj === 'object') {
|
|
58
|
+
const resolved: Record<string, unknown> = {};
|
|
59
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
60
|
+
resolved[key] = this.resolveAll(value, context);
|
|
61
|
+
}
|
|
62
|
+
return resolved;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return obj;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get variable value with cascading priority
|
|
70
|
+
* Priority: iteration data > scope stack (innermost to outermost) > collection > environment > global
|
|
71
|
+
*/
|
|
72
|
+
private getVariable(name: string, context: ExecutionContext): unknown {
|
|
73
|
+
// 1. Iteration data (highest priority)
|
|
74
|
+
const currentIterationData = context.iterationData?.[context.iterationCurrent - 1];
|
|
75
|
+
if (currentIterationData !== null && currentIterationData !== undefined && name in currentIterationData) {
|
|
76
|
+
this.logger.trace(`Variable '${name}' found in iteration data`);
|
|
77
|
+
return currentIterationData[name];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 2. Scope stack (hierarchical scope variables - search from innermost to outermost)
|
|
81
|
+
// This represents quest.scope.variables which flows through the script inheritance chain
|
|
82
|
+
if (context.scopeStack !== null && context.scopeStack !== undefined && context.scopeStack.length > 0) {
|
|
83
|
+
// Search from top of stack (most specific) to bottom (least specific)
|
|
84
|
+
for (let i = context.scopeStack.length - 1; i >= 0; i--) {
|
|
85
|
+
const frame = context.scopeStack[i];
|
|
86
|
+
if (name in frame.vars) {
|
|
87
|
+
this.logger.trace(`Variable '${name}' found in scope stack (frame ${context.scopeStack.length - 1 - i})`);
|
|
88
|
+
return frame.vars[name];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 3. Collection variables
|
|
94
|
+
if (name in context.collectionVariables) {
|
|
95
|
+
this.logger.trace(`Variable '${name}' found in collection variables`);
|
|
96
|
+
return extractValue(context.collectionVariables[name]);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 4. Environment variables
|
|
100
|
+
if (context.environment !== null && context.environment !== undefined && name in context.environment.variables) {
|
|
101
|
+
this.logger.trace(`Variable '${name}' found in environment variables`);
|
|
102
|
+
return extractValue(context.environment.variables[name]);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 5. Global variables (lowest priority)
|
|
106
|
+
if (name in context.globalVariables) {
|
|
107
|
+
this.logger.trace(`Variable '${name}' found in global variables`);
|
|
108
|
+
return extractValue(context.globalVariables[name]);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this.logger.trace(`Variable '${name}' not found in any scope`);
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|