@apiquest/fracture 1.0.2 → 1.0.5
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 +207 -0
- package/bin/cli.js +2 -2
- package/dist/CollectionRunner.d.ts +2 -0
- package/dist/CollectionRunner.d.ts.map +1 -1
- package/dist/CollectionRunner.js +23 -5
- package/dist/CollectionRunner.js.map +1 -1
- package/dist/LibraryLoader.d.ts +49 -0
- package/dist/LibraryLoader.d.ts.map +1 -0
- package/dist/LibraryLoader.js +198 -0
- package/dist/LibraryLoader.js.map +1 -0
- package/dist/PluginLoader.d.ts.map +1 -1
- package/dist/PluginLoader.js +9 -6
- package/dist/PluginLoader.js.map +1 -1
- package/dist/PluginResolver.d.ts +1 -1
- package/dist/PluginResolver.d.ts.map +1 -1
- package/dist/PluginResolver.js +1 -1
- package/dist/PluginResolver.js.map +1 -1
- package/dist/ScriptEngine.d.ts +2 -1
- package/dist/ScriptEngine.d.ts.map +1 -1
- package/dist/ScriptEngine.js +19 -12
- package/dist/ScriptEngine.js.map +1 -1
- package/dist/cli/index.js +35 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/plugin-commands.d.ts.map +1 -1
- package/dist/cli/plugin-commands.js +48 -81
- package/dist/cli/plugin-commands.js.map +1 -1
- package/dist/cli/plugin-installer.d.ts +48 -0
- package/dist/cli/plugin-installer.d.ts.map +1 -0
- package/dist/cli/plugin-installer.js +136 -0
- package/dist/cli/plugin-installer.js.map +1 -0
- package/dist/cli/plugin-registry.d.ts +17 -0
- package/dist/cli/plugin-registry.d.ts.map +1 -0
- package/dist/cli/plugin-registry.js +77 -0
- package/dist/cli/plugin-registry.js.map +1 -0
- package/package.json +55 -50
- 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/CollectionAnalyzer.ts +0 -102
- package/src/CollectionRunner.ts +0 -1423
- package/src/CollectionRunner.types.ts +0 -9
- package/src/CollectionValidator.ts +0 -289
- package/src/ConsoleReporter.ts +0 -143
- package/src/CookieJar.ts +0 -258
- package/src/DagScheduler.ts +0 -439
- package/src/Logger.ts +0 -85
- package/src/PluginLoader.ts +0 -126
- package/src/PluginManager.ts +0 -208
- package/src/PluginResolver.ts +0 -154
- package/src/QuestAPI.ts +0 -764
- package/src/QuestAPI.types.ts +0 -33
- package/src/QuestTestAPI.ts +0 -164
- package/src/RequestFilter.ts +0 -224
- package/src/ScriptEngine.ts +0 -219
- package/src/ScriptValidator.ts +0 -428
- package/src/TaskGraph.ts +0 -598
- package/src/TestCounter.ts +0 -109
- package/src/VariableResolver.ts +0 -114
- package/src/cli/index.ts +0 -480
- package/src/cli/plugin-commands.ts +0 -341
- package/src/cli/plugin-discovery.ts +0 -44
- package/src/index.ts +0 -24
- package/src/utils.ts +0 -52
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
Collection,
|
|
3
|
-
CollectionItem,
|
|
4
|
-
Request,
|
|
5
|
-
Folder,
|
|
6
|
-
RuntimeOptions,
|
|
7
|
-
ValidationResult,
|
|
8
|
-
ValidationError,
|
|
9
|
-
} from '@apiquest/types';
|
|
10
|
-
import { ScriptType } from '@apiquest/types';
|
|
11
|
-
import { ScriptValidator } from './ScriptValidator.js';
|
|
12
|
-
import type { PluginManager } from './PluginManager.js';
|
|
13
|
-
import { Logger } from './Logger.js';
|
|
14
|
-
import { isNullOrWhitespace } from './utils.js';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Validates collections and their items (folders/requests) for pre-run validation
|
|
18
|
-
*/
|
|
19
|
-
export class CollectionValidator {
|
|
20
|
-
private logger: Logger;
|
|
21
|
-
|
|
22
|
-
constructor(
|
|
23
|
-
private readonly pluginManager: PluginManager,
|
|
24
|
-
baseLogger?: Logger
|
|
25
|
-
) {
|
|
26
|
-
this.logger = baseLogger?.createLogger('CollectionValidator') ?? new Logger('CollectionValidator');
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Validate entire collection structure, scripts, and configurations
|
|
31
|
-
*/
|
|
32
|
-
async validateCollection(
|
|
33
|
-
collection: Collection,
|
|
34
|
-
options: RuntimeOptions,
|
|
35
|
-
strictMode: boolean = true
|
|
36
|
-
): Promise<ValidationResult> {
|
|
37
|
-
const errors: ValidationError[] = [];
|
|
38
|
-
|
|
39
|
-
this.logger.debug(`Validating collection: ${collection.info.name} (strict=${strictMode})`);
|
|
40
|
-
|
|
41
|
-
// Get protocol plugin for validation
|
|
42
|
-
const protocolPlugin = this.pluginManager.getPlugin(collection.protocol);
|
|
43
|
-
if (protocolPlugin === undefined) {
|
|
44
|
-
this.logger.warn(`Protocol plugin not loaded for validation: ${collection.protocol}`);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Helper to recursively validate all items
|
|
48
|
-
const validateItem = (item: CollectionItem, path: string): void => {
|
|
49
|
-
if (item.type === 'folder') {
|
|
50
|
-
const folder = item;
|
|
51
|
-
|
|
52
|
-
// Validate folder scripts
|
|
53
|
-
if (!isNullOrWhitespace(folder.folderPreScript)) {
|
|
54
|
-
errors.push(
|
|
55
|
-
...ScriptValidator.validateScript(
|
|
56
|
-
folder.folderPreScript!,
|
|
57
|
-
ScriptType.FolderPre,
|
|
58
|
-
path,
|
|
59
|
-
undefined,
|
|
60
|
-
protocolPlugin,
|
|
61
|
-
strictMode
|
|
62
|
-
)
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
if (!isNullOrWhitespace(folder.folderPostScript)) {
|
|
66
|
-
errors.push(
|
|
67
|
-
...ScriptValidator.validateScript(
|
|
68
|
-
folder.folderPostScript!,
|
|
69
|
-
ScriptType.FolderPost,
|
|
70
|
-
path,
|
|
71
|
-
undefined,
|
|
72
|
-
protocolPlugin,
|
|
73
|
-
strictMode
|
|
74
|
-
)
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
if (!isNullOrWhitespace(folder.preRequestScript)) {
|
|
78
|
-
errors.push(
|
|
79
|
-
...ScriptValidator.validateScript(
|
|
80
|
-
folder.preRequestScript!,
|
|
81
|
-
ScriptType.PreRequest,
|
|
82
|
-
path,
|
|
83
|
-
undefined,
|
|
84
|
-
protocolPlugin,
|
|
85
|
-
strictMode
|
|
86
|
-
)
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
if (!isNullOrWhitespace(folder.postRequestScript)) {
|
|
90
|
-
errors.push(
|
|
91
|
-
...ScriptValidator.validateScript(
|
|
92
|
-
folder.postRequestScript!,
|
|
93
|
-
ScriptType.PostRequest,
|
|
94
|
-
path,
|
|
95
|
-
undefined,
|
|
96
|
-
protocolPlugin,
|
|
97
|
-
strictMode
|
|
98
|
-
)
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Validate auth config if present
|
|
103
|
-
if (folder.auth !== null && folder.auth !== undefined && folder.auth.type !== 'inherit' && folder.auth.type !== 'none') {
|
|
104
|
-
const authPlugin = this.pluginManager.getAuthPlugin(folder.auth.type);
|
|
105
|
-
if (authPlugin?.validate !== null && authPlugin?.validate !== undefined) {
|
|
106
|
-
const authResult = authPlugin.validate(folder.auth, options);
|
|
107
|
-
if (authResult.valid === false && authResult.errors !== null && authResult.errors !== undefined) {
|
|
108
|
-
errors.push(...authResult.errors);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Recursively validate folder items
|
|
114
|
-
for (const child of folder.items) {
|
|
115
|
-
const childPath = `${path}/${child.name}`;
|
|
116
|
-
validateItem(child, childPath);
|
|
117
|
-
}
|
|
118
|
-
} else {
|
|
119
|
-
// Request validation
|
|
120
|
-
const request = item;
|
|
121
|
-
|
|
122
|
-
// Validate request scripts
|
|
123
|
-
if (!isNullOrWhitespace(request.preRequestScript)) {
|
|
124
|
-
errors.push(
|
|
125
|
-
...ScriptValidator.validateScript(
|
|
126
|
-
request.preRequestScript!,
|
|
127
|
-
ScriptType.PreRequest,
|
|
128
|
-
path,
|
|
129
|
-
undefined,
|
|
130
|
-
protocolPlugin,
|
|
131
|
-
strictMode
|
|
132
|
-
)
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
if (!isNullOrWhitespace(request.postRequestScript)) {
|
|
136
|
-
errors.push(
|
|
137
|
-
...ScriptValidator.validateScript(
|
|
138
|
-
request.postRequestScript!,
|
|
139
|
-
ScriptType.PostRequest,
|
|
140
|
-
path,
|
|
141
|
-
undefined,
|
|
142
|
-
protocolPlugin,
|
|
143
|
-
strictMode
|
|
144
|
-
)
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Validate plugin event scripts
|
|
149
|
-
if (request.data.scripts !== null && request.data.scripts !== undefined && Array.isArray(request.data.scripts)) {
|
|
150
|
-
// Check for duplicate event scripts (only one script per event type allowed)
|
|
151
|
-
const eventCounts = new Map<string, number>();
|
|
152
|
-
for (const script of request.data.scripts) {
|
|
153
|
-
const count = eventCounts.get(script.event) ?? 0;
|
|
154
|
-
eventCounts.set(script.event, count + 1);
|
|
155
|
-
|
|
156
|
-
if (count >= 1) {
|
|
157
|
-
errors.push({
|
|
158
|
-
message: `Request has multiple scripts for event "${script.event}". Only one script per event type is allowed.`,
|
|
159
|
-
location: path,
|
|
160
|
-
source: 'script'
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Validate each script
|
|
166
|
-
if (protocolPlugin?.events !== null && protocolPlugin?.events !== undefined) {
|
|
167
|
-
for (const script of request.data.scripts) {
|
|
168
|
-
const eventDef = protocolPlugin.events.find(e => e.name === script.event);
|
|
169
|
-
if (eventDef !== null && eventDef !== undefined) {
|
|
170
|
-
errors.push(
|
|
171
|
-
...ScriptValidator.validateScript(
|
|
172
|
-
script.script,
|
|
173
|
-
ScriptType.PluginEvent,
|
|
174
|
-
path,
|
|
175
|
-
eventDef,
|
|
176
|
-
protocolPlugin,
|
|
177
|
-
strictMode
|
|
178
|
-
)
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Validate protocol request via plugin
|
|
186
|
-
if (protocolPlugin?.validate !== null && protocolPlugin?.validate !== undefined) {
|
|
187
|
-
const protocolResult = protocolPlugin.validate(request, options);
|
|
188
|
-
if (protocolResult.valid === false && protocolResult.errors !== null && protocolResult.errors !== undefined) {
|
|
189
|
-
errors.push(
|
|
190
|
-
...protocolResult.errors.map(err => ({
|
|
191
|
-
...err,
|
|
192
|
-
location: path
|
|
193
|
-
}))
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Validate auth config if present
|
|
199
|
-
if (request.auth !== null && request.auth !== undefined && request.auth.type !== 'inherit' && request.auth.type !== 'none') {
|
|
200
|
-
const authPlugin = this.pluginManager.getAuthPlugin(request.auth.type);
|
|
201
|
-
if (authPlugin?.validate !== null && authPlugin?.validate !== undefined) {
|
|
202
|
-
const authResult = authPlugin.validate(request.auth, options);
|
|
203
|
-
if (authResult.valid === false && authResult.errors !== null && authResult.errors !== undefined) {
|
|
204
|
-
errors.push(
|
|
205
|
-
...authResult.errors.map(err => ({
|
|
206
|
-
...err,
|
|
207
|
-
location: path
|
|
208
|
-
}))
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
// Validate collection-level scripts
|
|
217
|
-
if (!isNullOrWhitespace(collection.collectionPreScript)) {
|
|
218
|
-
errors.push(
|
|
219
|
-
...ScriptValidator.validateScript(
|
|
220
|
-
collection.collectionPreScript!,
|
|
221
|
-
ScriptType.CollectionPre,
|
|
222
|
-
'/',
|
|
223
|
-
undefined,
|
|
224
|
-
protocolPlugin,
|
|
225
|
-
strictMode
|
|
226
|
-
)
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
if (!isNullOrWhitespace(collection.collectionPostScript)) {
|
|
230
|
-
errors.push(
|
|
231
|
-
...ScriptValidator.validateScript(
|
|
232
|
-
collection.collectionPostScript!,
|
|
233
|
-
ScriptType.CollectionPost,
|
|
234
|
-
'/',
|
|
235
|
-
undefined,
|
|
236
|
-
protocolPlugin,
|
|
237
|
-
strictMode
|
|
238
|
-
)
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
if (!isNullOrWhitespace(collection.preRequestScript)) {
|
|
242
|
-
errors.push(
|
|
243
|
-
...ScriptValidator.validateScript(
|
|
244
|
-
collection.preRequestScript!,
|
|
245
|
-
ScriptType.PreRequest,
|
|
246
|
-
'/',
|
|
247
|
-
undefined,
|
|
248
|
-
protocolPlugin,
|
|
249
|
-
strictMode
|
|
250
|
-
)
|
|
251
|
-
);
|
|
252
|
-
}
|
|
253
|
-
if (!isNullOrWhitespace(collection.postRequestScript)) {
|
|
254
|
-
errors.push(
|
|
255
|
-
...ScriptValidator.validateScript(
|
|
256
|
-
collection.postRequestScript!,
|
|
257
|
-
ScriptType.PostRequest,
|
|
258
|
-
'/',
|
|
259
|
-
undefined,
|
|
260
|
-
protocolPlugin,
|
|
261
|
-
strictMode
|
|
262
|
-
)
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Validate collection-level auth
|
|
267
|
-
if (collection.auth !== null && collection.auth !== undefined && collection.auth.type !== 'inherit' && collection.auth.type !== 'none') {
|
|
268
|
-
const authPlugin = this.pluginManager.getAuthPlugin(collection.auth.type);
|
|
269
|
-
if (authPlugin?.validate !== null && authPlugin?.validate !== undefined) {
|
|
270
|
-
const authResult = authPlugin.validate(collection.auth, options);
|
|
271
|
-
if (authResult.valid === false && authResult.errors !== null && authResult.errors !== undefined) {
|
|
272
|
-
errors.push(...authResult.errors);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Validate all items recursively
|
|
278
|
-
for (const item of collection.items) {
|
|
279
|
-
validateItem(item, `/${item.name}`);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
this.logger.debug(`Validation completed with ${errors.length} error(s)`);
|
|
283
|
-
|
|
284
|
-
return {
|
|
285
|
-
valid: errors.length === 0,
|
|
286
|
-
errors
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
}
|
package/src/ConsoleReporter.ts
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
IReporter,
|
|
3
|
-
Collection,
|
|
4
|
-
RunOptions,
|
|
5
|
-
Request,
|
|
6
|
-
RequestResult,
|
|
7
|
-
TestResult,
|
|
8
|
-
RunResult,
|
|
9
|
-
LogLevel,
|
|
10
|
-
EventPayloads
|
|
11
|
-
} from '@apiquest/types';
|
|
12
|
-
import { LogLevel as LogLevelEnum } from '@apiquest/types';
|
|
13
|
-
import type { EventEmitter } from 'events';
|
|
14
|
-
|
|
15
|
-
export class ConsoleReporter implements IReporter {
|
|
16
|
-
name = 'console';
|
|
17
|
-
version: string = '1.0.0';
|
|
18
|
-
description: string = 'Pretty CLI output for quest runs';
|
|
19
|
-
reportTypes: string[] = ['console'];
|
|
20
|
-
|
|
21
|
-
private logLevel: LogLevel;
|
|
22
|
-
private color: boolean;
|
|
23
|
-
private runner?: EventEmitter;
|
|
24
|
-
|
|
25
|
-
constructor(options?: { logLevel?: LogLevel; color?: boolean; runner?: EventEmitter }) {
|
|
26
|
-
this.logLevel = options?.logLevel ?? LogLevelEnum.INFO;
|
|
27
|
-
this.color = options?.color ?? true; // Color enabled by default
|
|
28
|
-
this.runner = options?.runner;
|
|
29
|
-
|
|
30
|
-
// Subscribe to console events for logger output
|
|
31
|
-
if (this.runner !== null && this.runner !== undefined) {
|
|
32
|
-
this.setupConsoleLogging();
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
private setupConsoleLogging(): void {
|
|
37
|
-
if (this.runner === null || this.runner === undefined) return;
|
|
38
|
-
|
|
39
|
-
// Log levels are hierarchical using LogLevel enum values
|
|
40
|
-
this.runner.on('console', ({ level, levelName, message }: EventPayloads['console']) => {
|
|
41
|
-
// Only show if message level is <= configured level (lower number = higher priority)
|
|
42
|
-
if (level <= this.logLevel) {
|
|
43
|
-
const tag = `[${(levelName !== null && levelName !== undefined && levelName.length > 0) ? levelName.toUpperCase() : 'LOG'}]`;
|
|
44
|
-
if (level === LogLevelEnum.ERROR) {
|
|
45
|
-
console.error(`${tag} ${message}`);
|
|
46
|
-
} else if (level === LogLevelEnum.WARN) {
|
|
47
|
-
console.warn(`${tag} ${message}`);
|
|
48
|
-
} else {
|
|
49
|
-
console.log(`${tag} ${message}`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
private colorize(text: string, colorCode: string): string {
|
|
56
|
-
if (!this.color) return text;
|
|
57
|
-
return `${colorCode}${text}\x1b[0m`;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
onRunStarted(collection: Collection, options: RunOptions): void {
|
|
61
|
-
console.log('============================================================');
|
|
62
|
-
console.log(` Quest v1.0.0`);
|
|
63
|
-
console.log(` Collection: ${collection.info.name}`);
|
|
64
|
-
console.log('============================================================');
|
|
65
|
-
console.log('');
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
onBeforeRequest(payload: EventPayloads['beforeRequest']): void {
|
|
69
|
-
console.log('');
|
|
70
|
-
console.log(`${this.colorize('>', '\x1b[36m')} ${payload.request.name}`);
|
|
71
|
-
|
|
72
|
-
const requestData = payload.request.data as Record<string, unknown> | null | undefined;
|
|
73
|
-
if (requestData !== null && requestData !== undefined) {
|
|
74
|
-
const method = (typeof requestData.method === 'string' && requestData.method.length > 0) ? requestData.method : 'GET';
|
|
75
|
-
const url = (typeof requestData.url === 'string' && requestData.url.length > 0) ? requestData.url : '';
|
|
76
|
-
console.log(` ${method} ${url}`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
onAfterRequest(payload: EventPayloads['afterRequest']): void {
|
|
81
|
-
if (payload.response.error !== null && payload.response.error !== undefined && payload.response.error.length > 0) {
|
|
82
|
-
console.log(` ${this.colorize('[FAIL]', '\x1b[31m')} ERROR: ${payload.response.error}`);
|
|
83
|
-
} else {
|
|
84
|
-
const statusColor = payload.response.status >= 400 ? '\x1b[31m' : '\x1b[32m';
|
|
85
|
-
console.log(` ${this.colorize('<', statusColor)} ${payload.response.status} ${payload.response.statusText} (${payload.duration}ms)`);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
onAssertion(payload: EventPayloads['assertion']): void {
|
|
90
|
-
const test = payload.test;
|
|
91
|
-
if (test.skipped) {
|
|
92
|
-
console.log(` ${this.colorize('[SKIP]', '\x1b[90m')} ${test.name}`);
|
|
93
|
-
} else if (test.passed) {
|
|
94
|
-
console.log(` ${this.colorize('[PASS]', '\x1b[32m')} ${test.name}`);
|
|
95
|
-
} else {
|
|
96
|
-
console.log(` ${this.colorize('[FAIL]', '\x1b[31m')} ${test.name}`);
|
|
97
|
-
if (test.error !== null && test.error !== undefined && test.error.length > 0) {
|
|
98
|
-
console.log(` Error: ${test.error}`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
onRunCompleted(result: RunResult): void {
|
|
104
|
-
console.log('');
|
|
105
|
-
console.log('------------------------------------------------------------');
|
|
106
|
-
console.log('');
|
|
107
|
-
console.log('RESULTS:');
|
|
108
|
-
console.log(` Collection: ${result.collectionName}`);
|
|
109
|
-
console.log(` Duration: ${(result.duration / 1000).toFixed(2)}s`);
|
|
110
|
-
console.log(` Requests: ${result.requestResults.length}`);
|
|
111
|
-
|
|
112
|
-
const successful = result.requestResults.filter(r => r.success).length;
|
|
113
|
-
const failed = result.requestResults.filter(r => !r.success).length;
|
|
114
|
-
|
|
115
|
-
if (successful > 0) {
|
|
116
|
-
console.log(` - Successful: ${successful}`);
|
|
117
|
-
}
|
|
118
|
-
if (failed > 0) {
|
|
119
|
-
console.log(` - Failed: ${failed}`);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (result.totalTests > 0) {
|
|
123
|
-
console.log(` Tests: ${result.totalTests}`);
|
|
124
|
-
console.log(` - Passed: ${result.passedTests}`);
|
|
125
|
-
if (result.failedTests > 0) {
|
|
126
|
-
console.log(` - Failed: ${result.failedTests}`);
|
|
127
|
-
}
|
|
128
|
-
if (result.skippedTests > 0) {
|
|
129
|
-
console.log(` - Skipped: ${result.skippedTests}`);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
console.log('');
|
|
134
|
-
|
|
135
|
-
// if any TEST results failed end with error exit code 1
|
|
136
|
-
// Request failures are OK if tests expect and handle them
|
|
137
|
-
if (result.failedTests > 0) {
|
|
138
|
-
console.log(`${this.colorize('[FAIL]', '\x1b[31m')} Collection run completed with errors`); // Red
|
|
139
|
-
} else {
|
|
140
|
-
console.log(`${this.colorize('[PASS]', '\x1b[32m')} Collection run completed successfully`); // Green
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
package/src/CookieJar.ts
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
import { CookieJar as ToughCookieJar, Cookie } from 'tough-cookie';
|
|
2
|
-
import type { ICookieJar, CookieJarOptions, Cookie as ICookie, CookieSetOptions } from '@apiquest/types';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* CookieJar implementation using tough-cookie for production-quality cookie management.
|
|
6
|
-
* Wraps tough-cookie's CookieJar to provide ICookieJar interface.
|
|
7
|
-
*/
|
|
8
|
-
export class CookieJar implements ICookieJar {
|
|
9
|
-
private jar: ToughCookieJar;
|
|
10
|
-
private options: CookieJarOptions;
|
|
11
|
-
|
|
12
|
-
constructor(options?: CookieJarOptions) {
|
|
13
|
-
this.options = options ?? { persist: false };
|
|
14
|
-
this.jar = new ToughCookieJar();
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Store cookies from Set-Cookie headers
|
|
19
|
-
* @param setCookieHeaders - Single header string or array of header strings
|
|
20
|
-
* @param requestUrl - URL the cookies came from (REQUIRED for domain/path matching)
|
|
21
|
-
*/
|
|
22
|
-
store(setCookieHeaders: string | string[] | null | undefined, requestUrl: string): void {
|
|
23
|
-
if (setCookieHeaders === null || setCookieHeaders === undefined) {
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const headers = Array.isArray(setCookieHeaders) ? setCookieHeaders : [setCookieHeaders];
|
|
28
|
-
|
|
29
|
-
for (const header of headers) {
|
|
30
|
-
try {
|
|
31
|
-
this.jar.setCookieSync(header, requestUrl);
|
|
32
|
-
} catch (error) {
|
|
33
|
-
// Ignore invalid cookies (tough-cookie throws on malformed cookies)
|
|
34
|
-
// Silent failure is fine here
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Get cookie value by name
|
|
41
|
-
* When called without domain, searches ALL cookies (across all domains)
|
|
42
|
-
*
|
|
43
|
-
* @param name - Cookie name
|
|
44
|
-
* @param domain - Optional domain to filter (not commonly used)
|
|
45
|
-
* @param path - Optional path to filter (not commonly used)
|
|
46
|
-
* @returns Cookie value or null if not found
|
|
47
|
-
*/
|
|
48
|
-
get(name: string, domain?: string, path?: string): string | null {
|
|
49
|
-
// Use toJSON to get ALL cookies, then manually filter for expiration only
|
|
50
|
-
const allCookiesJson = this.jar.toJSON();
|
|
51
|
-
if (allCookiesJson?.cookies === undefined) {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Search for cookie by name
|
|
56
|
-
for (const cookieData of allCookiesJson.cookies) {
|
|
57
|
-
// Use Cookie.fromJSON for proper typing
|
|
58
|
-
const cookie = Cookie.fromJSON(cookieData);
|
|
59
|
-
if (cookie === null || cookie === undefined) {
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (cookie.key !== name) {
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Check expiration
|
|
68
|
-
const expiryTime = cookie.expiryTime();
|
|
69
|
-
if (expiryTime !== null && expiryTime !== undefined && expiryTime < Date.now()) {
|
|
70
|
-
continue; // Skip expired cookies
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// If domain filter specified, check it
|
|
74
|
-
if (domain !== null && domain !== undefined && domain !== '') {
|
|
75
|
-
if (cookie.domain === null) {
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
if (cookie.domain !== domain && !cookie.domain.endsWith(domain)) {
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// If path filter specified, check it
|
|
84
|
-
if (path !== null && path !== undefined && path !== '') {
|
|
85
|
-
if (cookie.path === null || cookie.path !== path) {
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return cookie.value;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Check if cookie exists
|
|
98
|
-
* @param name - Cookie name
|
|
99
|
-
* @param domain - Optional domain filter
|
|
100
|
-
* @param path - Optional path filter
|
|
101
|
-
* @returns true if cookie exists
|
|
102
|
-
*/
|
|
103
|
-
has(name: string, domain?: string, path?: string): boolean {
|
|
104
|
-
return this.get(name, domain, path) !== null;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Remove a cookie by name
|
|
109
|
-
* @param name - Cookie name
|
|
110
|
-
* @param domain - Optional domain
|
|
111
|
-
* @param path - Optional path
|
|
112
|
-
*/
|
|
113
|
-
remove(name: string, domain?: string, path?: string): void {
|
|
114
|
-
const allCookiesJson = this.jar.toJSON();
|
|
115
|
-
if (allCookiesJson?.cookies === undefined) {
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Find matching cookies using Cookie.fromJSON for proper typing
|
|
120
|
-
const cookiesToRemove: Cookie[] = [];
|
|
121
|
-
for (const cookieData of allCookiesJson.cookies) {
|
|
122
|
-
const cookie = Cookie.fromJSON(cookieData);
|
|
123
|
-
if (cookie === null || cookie === undefined) {
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (cookie.key !== name) {
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Check domain filter
|
|
132
|
-
if (domain !== null && domain !== undefined && domain !== '') {
|
|
133
|
-
if (cookie.domain === null) {
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
if (cookie.domain !== domain && !cookie.domain.endsWith(domain)) {
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Check path filter
|
|
142
|
-
if (path !== null && path !== undefined && path !== '') {
|
|
143
|
-
if (cookie.path === null || cookie.path !== path) {
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
cookiesToRemove.push(cookie);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Remove each matching cookie
|
|
152
|
-
for (const cookie of cookiesToRemove) {
|
|
153
|
-
// Domain should always be present in cookies from tough-cookie
|
|
154
|
-
const cookieDomain = cookie.domain;
|
|
155
|
-
if (cookieDomain === null || cookieDomain === undefined) {
|
|
156
|
-
continue; // Skip cookies without domain
|
|
157
|
-
}
|
|
158
|
-
const cookiePath = cookie.path ?? '/';
|
|
159
|
-
try {
|
|
160
|
-
this.jar.store.removeCookie(cookieDomain, cookiePath, cookie.key, () => {
|
|
161
|
-
// Callback required by tough-cookie API
|
|
162
|
-
});
|
|
163
|
-
} catch {
|
|
164
|
-
// Ignore errors
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Set a cookie manually
|
|
171
|
-
* Constructs URL from cookie's domain for RFC 6265 validation
|
|
172
|
-
* @param name - Cookie name
|
|
173
|
-
* @param value - Cookie value
|
|
174
|
-
* @param options - Cookie options
|
|
175
|
-
*/
|
|
176
|
-
set(name: string, value: string, options: CookieSetOptions): void {
|
|
177
|
-
let cookieStr = `${name}=${value}; Domain=${options.domain}; Path=${options.path ?? '/'}`;
|
|
178
|
-
|
|
179
|
-
if (options.expires !== null && options.expires !== undefined) {
|
|
180
|
-
cookieStr += `; Expires=${options.expires}`;
|
|
181
|
-
}
|
|
182
|
-
if (options.httpOnly === true) {
|
|
183
|
-
cookieStr += '; HttpOnly';
|
|
184
|
-
}
|
|
185
|
-
if (options.secure === true) {
|
|
186
|
-
cookieStr += '; Secure';
|
|
187
|
-
}
|
|
188
|
-
if (options.sameSite !== null && options.sameSite !== undefined) {
|
|
189
|
-
cookieStr += `; SameSite=${options.sameSite}`;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const protocol = options.secure === true ? 'https' : 'http';
|
|
193
|
-
const url = `${protocol}://${options.domain}${options.path ?? '/'}`;
|
|
194
|
-
|
|
195
|
-
this.jar.setCookieSync(cookieStr, url);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Clear all cookies
|
|
200
|
-
*/
|
|
201
|
-
clear(): void {
|
|
202
|
-
this.jar.removeAllCookiesSync();
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Get all cookies as an object
|
|
207
|
-
* Returns non-expired cookies from ALL domains
|
|
208
|
-
* @returns Object with cookie names as keys and values
|
|
209
|
-
*/
|
|
210
|
-
toObject(): Record<string, string> {
|
|
211
|
-
const result: Record<string, string> = {};
|
|
212
|
-
|
|
213
|
-
// Use toJSON to get all cookies, filter expired manually
|
|
214
|
-
const allCookiesJson = this.jar.toJSON();
|
|
215
|
-
if (allCookiesJson?.cookies === undefined) {
|
|
216
|
-
return result;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Add all non-expired cookies
|
|
220
|
-
for (const cookieData of allCookiesJson.cookies) {
|
|
221
|
-
try {
|
|
222
|
-
const cookie = Cookie.fromJSON(cookieData);
|
|
223
|
-
if (cookie === null || cookie === undefined) {
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const expiryTime = cookie.expiryTime();
|
|
228
|
-
// Not expired if: no expiry time OR expiry time is in the future
|
|
229
|
-
const isExpired = expiryTime !== null && expiryTime !== undefined && expiryTime < Date.now();
|
|
230
|
-
if (!isExpired) {
|
|
231
|
-
result[cookie.key] = cookie.value;
|
|
232
|
-
}
|
|
233
|
-
} catch {
|
|
234
|
-
// Skip cookies that can't be parsed
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return result;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Get Cookie header string for a URL
|
|
243
|
-
* This is the main method used by HTTP plugin to send cookies with requests
|
|
244
|
-
* @param url - URL to get cookies for
|
|
245
|
-
* @returns Cookie header string in "name1=value1; name2=value2" format, or null if no cookies
|
|
246
|
-
*/
|
|
247
|
-
getCookieHeader(url: string): string | null {
|
|
248
|
-
try {
|
|
249
|
-
const cookieString = this.jar.getCookieStringSync(url);
|
|
250
|
-
if (cookieString === null || cookieString === undefined || cookieString === '') {
|
|
251
|
-
return null;
|
|
252
|
-
}
|
|
253
|
-
return cookieString;
|
|
254
|
-
} catch {
|
|
255
|
-
return null;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|