@nestia/e2e 14.0.0 → 14.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/README.md +1 -1
- package/lib/ArrayUtil.d.ts +152 -70
- package/lib/ArrayUtil.js +169 -75
- package/lib/ArrayUtil.js.map +1 -1
- package/lib/DynamicExecutor.d.ts +139 -11
- package/lib/DynamicExecutor.js +98 -10
- package/lib/DynamicExecutor.js.map +1 -1
- package/lib/GaffComparator.d.ts +176 -73
- package/lib/GaffComparator.js +196 -77
- package/lib/GaffComparator.js.map +1 -1
- package/lib/MapUtil.d.ts +37 -6
- package/lib/MapUtil.js +37 -6
- package/lib/MapUtil.js.map +1 -1
- package/lib/RandomGenerator.d.ts +157 -84
- package/lib/RandomGenerator.js +173 -89
- package/lib/RandomGenerator.js.map +1 -1
- package/lib/TestValidator.d.ts +253 -74
- package/lib/TestValidator.js +274 -97
- package/lib/TestValidator.js.map +1 -1
- package/lib/internal/json_equal_to.d.ts +21 -0
- package/lib/internal/json_equal_to.js +35 -6
- package/lib/internal/json_equal_to.js.map +1 -1
- package/package.json +3 -2
- package/src/ArrayUtil.ts +173 -77
- package/src/DynamicExecutor.ts +158 -15
- package/src/GaffComparator.ts +195 -77
- package/src/MapUtil.ts +37 -6
- package/src/RandomGenerator.ts +172 -89
- package/src/TestValidator.ts +302 -107
- package/src/internal/json_equal_to.ts +36 -4
package/lib/TestValidator.js
CHANGED
|
@@ -25,18 +25,31 @@ const json_equal_to_1 = require("./internal/json_equal_to");
|
|
|
25
25
|
* maintain currying patterns for advanced composition. All provide detailed
|
|
26
26
|
* error messages for debugging failed assertions.
|
|
27
27
|
*
|
|
28
|
+
* Processing cost: Scalar assertions use constant bookkeeping; JSON assertions
|
|
29
|
+
* traverse the expected structure, index compares the shared page prefix,
|
|
30
|
+
* search scans the dataset once per sample and sort checks adjacent results.
|
|
31
|
+
* External callbacks own their computation costs.
|
|
32
|
+
*
|
|
28
33
|
* @author Jeongho Nam - https://github.com/samchon
|
|
29
34
|
* @example
|
|
30
|
-
* ```typescript
|
|
31
35
|
* // Basic condition testing
|
|
32
|
-
* TestValidator.predicate(
|
|
36
|
+
* TestValidator.predicate(
|
|
37
|
+
* "user should be authenticated",
|
|
38
|
+
* user.isAuthenticated,
|
|
39
|
+
* );
|
|
33
40
|
*
|
|
34
41
|
* // Equality validation
|
|
35
42
|
* TestValidator.equals("API response should match expected", x, y);
|
|
36
43
|
*
|
|
37
44
|
* // Error validation
|
|
38
|
-
* TestValidator.error("should throw on invalid input", () =>
|
|
39
|
-
*
|
|
45
|
+
* TestValidator.error("should throw on invalid input", () =>
|
|
46
|
+
* assertInput(""),
|
|
47
|
+
* );
|
|
48
|
+
*
|
|
49
|
+
* @evidence contracts/common.md#principled-implementation Each validator runs a check and throws an `Error` whose message begins with `Bug on <title>`, returning a promise only when its input is asynchronous, so the same function serves synchronous and asynchronous tests; the comparison helpers read values through their JSON form, which is the form an HTTP API delivers.
|
|
50
|
+
* @evidence contracts/common.md#clear-and-simple-design One function per assertion kind, sharing the JSON comparison (`json_equal_to`) and two small module helpers (`is_promise` and `is_sorted`), with the curried forms only where a request must be prepared once and run twice.
|
|
51
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts Assertions compare the values they are given; none consults a fixture, an entity name, or an environment.
|
|
52
|
+
* @evidence contracts/common.md#meaningful-documentation The namespace prose lists the assertion families and the error message convention, with examples.
|
|
40
53
|
*/
|
|
41
54
|
var TestValidator;
|
|
42
55
|
(function (TestValidator) {
|
|
@@ -47,19 +60,33 @@ var TestValidator;
|
|
|
47
60
|
* boolean, and asynchronous functions returning Promise<boolean>. The return
|
|
48
61
|
* type is automatically inferred based on the input type.
|
|
49
62
|
*
|
|
63
|
+
* Processing cost: The condition is evaluated once and asynchronous
|
|
64
|
+
* settlement adds constant bookkeeping without polling or repeating the
|
|
65
|
+
* condition.
|
|
66
|
+
*
|
|
67
|
+
* Reuse: Each invocation must evaluate its supplied task or request to
|
|
68
|
+
* observe current behavior; prior results cannot replace these effectful
|
|
69
|
+
* checks.
|
|
70
|
+
*
|
|
71
|
+
* Resource ownership: The returned promise retains invocation-local state
|
|
72
|
+
* until settlement. The callback owns its external resources, and this
|
|
73
|
+
* validator provides no cancellation or timeout.
|
|
74
|
+
*
|
|
50
75
|
* @example
|
|
51
|
-
* ```typescript
|
|
52
76
|
* // Synchronous boolean
|
|
53
77
|
* TestValidator.predicate("user should exist", user !== null);
|
|
54
78
|
*
|
|
55
79
|
* // Synchronous function
|
|
56
|
-
* TestValidator.predicate(
|
|
80
|
+
* TestValidator.predicate(
|
|
81
|
+
* "array should be empty",
|
|
82
|
+
* () => arr.length === 0,
|
|
83
|
+
* );
|
|
57
84
|
*
|
|
58
85
|
* // Asynchronous function
|
|
59
|
-
* await TestValidator.predicate(
|
|
60
|
-
*
|
|
86
|
+
* await TestValidator.predicate(
|
|
87
|
+
* "database should be connected",
|
|
88
|
+
* async () => await db.ping(),
|
|
61
89
|
* );
|
|
62
|
-
* ```;
|
|
63
90
|
*
|
|
64
91
|
* @param title - Descriptive title used in error messages when validation
|
|
65
92
|
* fails
|
|
@@ -67,6 +94,10 @@ var TestValidator;
|
|
|
67
94
|
* function)
|
|
68
95
|
* @returns Void or Promise<void> based on the input type
|
|
69
96
|
* @throws Error with descriptive message when condition is not satisfied
|
|
97
|
+
* @evidence contracts/common.md#principled-implementation A boolean, a function returning a boolean, or a function returning a promise of a boolean is evaluated, and only the exact value `true` passes; a false result throws the same `Error` in all three forms, and the asynchronous form rejects with it.
|
|
98
|
+
* @evidence contracts/common.md#clear-and-simple-design One function whose three branches differ only in when the value is available.
|
|
99
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts The check is on the value the caller gives, with no truthiness coercion.
|
|
100
|
+
* @evidence contracts/common.md#meaningful-documentation The comment documents the three forms, the parameters, the return type rule, and the thrown error, with examples.
|
|
70
101
|
*/
|
|
71
102
|
function predicate(title, condition) {
|
|
72
103
|
const message = () => `Bug on ${title}: expected condition is not satisfied.`;
|
|
@@ -90,7 +121,7 @@ var TestValidator;
|
|
|
90
121
|
if (flag === true)
|
|
91
122
|
resolve();
|
|
92
123
|
else
|
|
93
|
-
reject(message());
|
|
124
|
+
reject(new Error(message()));
|
|
94
125
|
})
|
|
95
126
|
.catch(reject);
|
|
96
127
|
});
|
|
@@ -99,30 +130,45 @@ var TestValidator;
|
|
|
99
130
|
/**
|
|
100
131
|
* Validates deep equality between two values using JSON comparison.
|
|
101
132
|
*
|
|
102
|
-
* Performs recursive comparison of objects and arrays
|
|
133
|
+
* Performs recursive comparison of objects and arrays through their JSON
|
|
134
|
+
* form. The comparison walks the keys of the first value, so a key that only
|
|
135
|
+
* the second value has is ignored, which is why the second value may be a
|
|
136
|
+
* subtype of the first. Keys whose value in the first value is `undefined`
|
|
137
|
+
* are skipped, and arrays must have the same length. Supports an optional
|
|
103
138
|
* exception filter to ignore specific keys during comparison. Useful for
|
|
104
139
|
* validating API responses, data transformations, and object state changes.
|
|
105
140
|
*
|
|
141
|
+
* Processing cost: The shared JSON walker traverses the first value and its
|
|
142
|
+
* corresponding second-value positions once, with recursion and accessor
|
|
143
|
+
* output proportional to visited structure; serialization occurs only on
|
|
144
|
+
* failure.
|
|
145
|
+
*
|
|
106
146
|
* @example
|
|
107
|
-
* ```typescript
|
|
108
147
|
* // Basic equality
|
|
109
|
-
* TestValidator.equals(
|
|
148
|
+
* TestValidator.equals(
|
|
149
|
+
* "response should match expected",
|
|
150
|
+
* expectedUser,
|
|
151
|
+
* actualUser,
|
|
152
|
+
* );
|
|
110
153
|
*
|
|
111
154
|
* // Ignore timestamps in comparison
|
|
112
|
-
* TestValidator.equals(
|
|
113
|
-
*
|
|
155
|
+
* TestValidator.equals(
|
|
156
|
+
* "user data should match",
|
|
157
|
+
* expectedUser,
|
|
158
|
+
* actualUser,
|
|
159
|
+
* (key) => key === "updatedAt",
|
|
114
160
|
* );
|
|
115
161
|
*
|
|
116
162
|
* // Validate API response structure
|
|
117
|
-
* TestValidator.equals(
|
|
163
|
+
* TestValidator.equals(
|
|
164
|
+
* "API response structure",
|
|
165
|
+
* { id: 1, name: "John" },
|
|
118
166
|
* { id: 1, name: "John" },
|
|
119
|
-
* { id: 1, name: "John" }
|
|
120
167
|
* );
|
|
121
168
|
*
|
|
122
169
|
* // Type-safe nullable comparisons
|
|
123
170
|
* const nullableData: { name: string } | null = getData();
|
|
124
171
|
* TestValidator.equals("nullable check", nullableData, null);
|
|
125
|
-
* ```;
|
|
126
172
|
*
|
|
127
173
|
* @param title - Descriptive title used in error messages when values differ
|
|
128
174
|
* @param X - The first value to compare
|
|
@@ -130,6 +176,10 @@ var TestValidator;
|
|
|
130
176
|
* @param exception - Optional filter function to exclude specific keys from
|
|
131
177
|
* comparison
|
|
132
178
|
* @throws Error with detailed diff information when values are not equal
|
|
179
|
+
* @evidence contracts/common.md#principled-implementation Both values are compared through `json_equal_to`, which walks the keys of the first value, so the second value may carry extra keys and the typing `Y extends X` states that; the differing accessors are listed in the error with both values serialized.
|
|
180
|
+
* @evidence contracts/common.md#clear-and-simple-design A thin wrapper over the shared comparison that adds the message.
|
|
181
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts The exception filter is the caller's; the function contains no key name of its own.
|
|
182
|
+
* @evidence contracts/common.md#meaningful-documentation The comment states the JSON form, the key-walking rule with its consequence for extra keys, the array length rule, and the exception filter, with examples.
|
|
133
183
|
*/
|
|
134
184
|
function equals(title, X, y, exception) {
|
|
135
185
|
const diff = (0, json_equal_to_1.json_equal_to)(exception !== null && exception !== void 0 ? exception : (() => false))(X)(y);
|
|
@@ -145,27 +195,42 @@ var TestValidator;
|
|
|
145
195
|
* Validates deep inequality between two values using JSON comparison.
|
|
146
196
|
*
|
|
147
197
|
* Performs recursive comparison of objects and arrays to ensure they are NOT
|
|
148
|
-
* equal
|
|
149
|
-
*
|
|
198
|
+
* equal, by the same rules as {@link equals}: only the keys of the first value
|
|
199
|
+
* are compared, so a key that only the second value has does not make them
|
|
200
|
+
* different. Supports an optional exception filter to ignore specific keys
|
|
201
|
+
* during comparison. Useful for validating that data has changed, objects are
|
|
150
202
|
* different, or mutations have occurred.
|
|
151
203
|
*
|
|
204
|
+
* Processing cost: The shared JSON walker visits the first value and
|
|
205
|
+
* corresponding positions once; a failure additionally serializes both values
|
|
206
|
+
* for diagnostics. Space follows visited structure and reported differences.
|
|
207
|
+
*
|
|
152
208
|
* @example
|
|
153
|
-
* ```typescript
|
|
154
209
|
* // Basic inequality
|
|
155
|
-
* TestValidator.notEquals(
|
|
210
|
+
* TestValidator.notEquals(
|
|
211
|
+
* "user should be different after update",
|
|
212
|
+
* originalUser,
|
|
213
|
+
* updatedUser,
|
|
214
|
+
* );
|
|
156
215
|
*
|
|
157
216
|
* // Ignore timestamps in comparison
|
|
158
|
-
* TestValidator.notEquals(
|
|
159
|
-
*
|
|
217
|
+
* TestValidator.notEquals(
|
|
218
|
+
* "user data should differ",
|
|
219
|
+
* originalUser,
|
|
220
|
+
* modifiedUser,
|
|
221
|
+
* (key) => key === "updatedAt",
|
|
160
222
|
* );
|
|
161
223
|
*
|
|
162
224
|
* // Validate state changes
|
|
163
|
-
* TestValidator.notEquals(
|
|
225
|
+
* TestValidator.notEquals(
|
|
226
|
+
* "state should have changed",
|
|
227
|
+
* initialState,
|
|
228
|
+
* currentState,
|
|
229
|
+
* );
|
|
164
230
|
*
|
|
165
231
|
* // Type-safe nullable comparisons
|
|
166
232
|
* const mutableData: { count: number } | null = getMutableData();
|
|
167
233
|
* TestValidator.notEquals("should have changed", mutableData, null);
|
|
168
|
-
* ```;
|
|
169
234
|
*
|
|
170
235
|
* @param title - Descriptive title used in error messages when values are
|
|
171
236
|
* equal
|
|
@@ -174,6 +239,10 @@ var TestValidator;
|
|
|
174
239
|
* @param exception - Optional filter function to exclude specific keys from
|
|
175
240
|
* comparison
|
|
176
241
|
* @throws Error when values are equal (indicating validation failure)
|
|
242
|
+
* @evidence contracts/common.md#principled-implementation It runs the same comparison as `equals` and fails when the comparison finds no difference, so two values differing only by extra keys of the second are considered equal and fail.
|
|
243
|
+
* @evidence contracts/common.md#clear-and-simple-design A thin wrapper over the shared comparison with the opposite outcome.
|
|
244
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts The exception filter is the caller's; the function contains no key name of its own.
|
|
245
|
+
* @evidence contracts/common.md#meaningful-documentation The comment states that the rules are those of `equals`, and gives examples.
|
|
177
246
|
*/
|
|
178
247
|
function notEquals(title, x, y, exception) {
|
|
179
248
|
const diff = (0, json_equal_to_1.json_equal_to)(exception !== null && exception !== void 0 ? exception : (() => false))(x)(y);
|
|
@@ -192,44 +261,57 @@ var TestValidator;
|
|
|
192
261
|
* successfully without throwing an error or rejecting, this validator will
|
|
193
262
|
* throw an exception. Supports both synchronous and asynchronous functions.
|
|
194
263
|
*
|
|
264
|
+
* Processing cost: The task is invoked once and its settlement is classified
|
|
265
|
+
* with constant bookkeeping; no retry or repeated execution occurs.
|
|
266
|
+
*
|
|
267
|
+
* Reuse: Each invocation must evaluate its supplied task or request to
|
|
268
|
+
* observe current behavior; prior results cannot replace these effectful
|
|
269
|
+
* checks.
|
|
270
|
+
*
|
|
271
|
+
* Resource ownership: The returned promise retains invocation-local state
|
|
272
|
+
* until settlement. The callback owns its external resources, and this
|
|
273
|
+
* validator provides no cancellation or timeout.
|
|
274
|
+
*
|
|
195
275
|
* @example
|
|
196
|
-
* ```typescript
|
|
197
276
|
* // Synchronous error validation
|
|
198
|
-
* TestValidator.error("should reject invalid email",
|
|
199
|
-
*
|
|
277
|
+
* TestValidator.error("should reject invalid email", () =>
|
|
278
|
+
* validateEmail("invalid-email"),
|
|
200
279
|
* );
|
|
201
280
|
*
|
|
202
281
|
* // Asynchronous error validation
|
|
203
|
-
* await TestValidator.error(
|
|
204
|
-
*
|
|
282
|
+
* await TestValidator.error(
|
|
283
|
+
* "should reject unauthorized access",
|
|
284
|
+
* async () => await api.functional.getSecretData(),
|
|
205
285
|
* );
|
|
206
286
|
*
|
|
207
287
|
* // Validate input validation
|
|
208
|
-
* TestValidator.error("should throw on empty string",
|
|
209
|
-
*
|
|
288
|
+
* TestValidator.error("should throw on empty string", () =>
|
|
289
|
+
* processRequiredInput(""),
|
|
210
290
|
* );
|
|
211
|
-
* ```;
|
|
212
291
|
*
|
|
213
292
|
* @param title - Descriptive title used in error messages when no error
|
|
214
293
|
* occurs
|
|
215
294
|
* @param task - The function that should throw an error or reject
|
|
216
295
|
* @returns Void or Promise<void> based on the input type
|
|
217
296
|
* @throws Error when the task function does not throw an error or reject
|
|
297
|
+
* @evidence contracts/common.md#principled-implementation The task is run inside a `try` so that only its own throw counts, a promise is awaited and a rejection counts as the expected error, and a task that neither throws nor rejects raises the failure outside the `try`, so the failure cannot be swallowed.
|
|
298
|
+
* @evidence contracts/common.md#clear-and-simple-design One function with a synchronous and an asynchronous path selected by the returned value.
|
|
299
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts Any error satisfies it, as documented, and no error type is special-cased.
|
|
300
|
+
* @evidence contracts/common.md#meaningful-documentation The comment states that an exception is required, the return type rule, and the thrown error, with examples.
|
|
218
301
|
*/
|
|
219
302
|
function error(title, task) {
|
|
220
303
|
const message = () => `Bug on ${title}: exception must be thrown.`;
|
|
304
|
+
// only the task's own throw is caught: the failure below must escape
|
|
305
|
+
let output;
|
|
221
306
|
try {
|
|
222
|
-
|
|
223
|
-
if (is_promise(output))
|
|
224
|
-
return new Promise((resolve, reject) => output
|
|
225
|
-
.catch(() => resolve())
|
|
226
|
-
.then(() => reject(new Error(message()))));
|
|
227
|
-
else
|
|
228
|
-
throw new Error(message());
|
|
307
|
+
output = task();
|
|
229
308
|
}
|
|
230
309
|
catch (_a) {
|
|
231
310
|
return undefined;
|
|
232
311
|
}
|
|
312
|
+
if (is_promise(output))
|
|
313
|
+
return new Promise((resolve, reject) => output.catch(() => resolve()).then(() => reject(new Error(message()))));
|
|
314
|
+
throw new Error(message());
|
|
233
315
|
}
|
|
234
316
|
TestValidator.error = error;
|
|
235
317
|
/**
|
|
@@ -239,23 +321,40 @@ var TestValidator;
|
|
|
239
321
|
* function throws an HttpError with one of the specified status codes. Useful
|
|
240
322
|
* for testing API endpoints, authentication, and authorization logic.
|
|
241
323
|
*
|
|
324
|
+
* Processing cost: The task is invoked once. Classification reads the thrown
|
|
325
|
+
* shape once and scans the expected status list until a match, using O(S)
|
|
326
|
+
* worst-case status comparisons and constant auxiliary state.
|
|
327
|
+
*
|
|
328
|
+
* Reuse: Each invocation must evaluate its supplied task or request to
|
|
329
|
+
* observe current behavior; prior results cannot replace these effectful
|
|
330
|
+
* checks.
|
|
331
|
+
*
|
|
332
|
+
* Resource ownership: The returned promise retains invocation-local state
|
|
333
|
+
* until settlement. The callback owns its external resources, and this
|
|
334
|
+
* validator provides no cancellation or timeout.
|
|
335
|
+
*
|
|
242
336
|
* @example
|
|
243
|
-
* ```typescript
|
|
244
337
|
* // Validate 401 Unauthorized
|
|
245
|
-
* await TestValidator.httpError(
|
|
246
|
-
*
|
|
338
|
+
* await TestValidator.httpError(
|
|
339
|
+
* "should return 401 for invalid token",
|
|
340
|
+
* 401,
|
|
341
|
+
* async () =>
|
|
342
|
+
* await api.functional.getProtectedResource("invalid-token"),
|
|
247
343
|
* );
|
|
248
344
|
*
|
|
249
345
|
* // Validate multiple possible error codes
|
|
250
|
-
* await TestValidator.httpError(
|
|
251
|
-
*
|
|
346
|
+
* await TestValidator.httpError(
|
|
347
|
+
* "should return client error",
|
|
348
|
+
* [400, 404, 422],
|
|
349
|
+
* async () => await api.functional.updateNonexistentResource(data),
|
|
252
350
|
* );
|
|
253
351
|
*
|
|
254
352
|
* // Validate server errors
|
|
255
|
-
* TestValidator.httpError(
|
|
256
|
-
*
|
|
353
|
+
* TestValidator.httpError(
|
|
354
|
+
* "should handle server errors",
|
|
355
|
+
* [500, 502, 503],
|
|
356
|
+
* () => callFaultyEndpoint(),
|
|
257
357
|
* );
|
|
258
|
-
* ```;
|
|
259
358
|
*
|
|
260
359
|
* @param title - Descriptive title used in error messages
|
|
261
360
|
* @param status - Expected status code(s), can be a single number or array
|
|
@@ -263,6 +362,10 @@ var TestValidator;
|
|
|
263
362
|
* @returns Void or Promise<void> based on the input type
|
|
264
363
|
* @throws Error when function doesn't throw HttpError or status code doesn't
|
|
265
364
|
* match
|
|
365
|
+
* @evidence contracts/common.md#principled-implementation The thrown value must expose a constructor named `HttpError` and one expected numeric status, matched by name because generated SDKs define their own classes. Missing or throwing properties make classification fail instead of escaping the expectation. The returned promise chain propagates rejection-handler failures without leaving a separately bridged promise pending.
|
|
366
|
+
* @evidence contracts/common.md#clear-and-simple-design One predicate reads the thrown value once and is shared by the synchronous and asynchronous paths; asynchronous settlement uses the task's own promise chain.
|
|
367
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts The check follows the documented error shape, not a specific SDK or status list.
|
|
368
|
+
* @evidence contracts/common.md#meaningful-documentation The comment documents the status argument, the two task kinds, and the thrown error, with examples.
|
|
266
369
|
*/
|
|
267
370
|
function httpError(title, status, task) {
|
|
268
371
|
if (typeof status === "number")
|
|
@@ -270,25 +373,36 @@ var TestValidator;
|
|
|
270
373
|
const message = (actual) => typeof actual === "number"
|
|
271
374
|
? `Bug on ${title}: status code must be ${status.join(" or ")}, but ${actual}.`
|
|
272
375
|
: `Bug on ${title}: status code must be ${status.join(" or ")}, but succeeded.`;
|
|
273
|
-
const predicate = (exp) =>
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
376
|
+
const predicate = (exp) => {
|
|
377
|
+
var _a;
|
|
378
|
+
let actual;
|
|
379
|
+
try {
|
|
380
|
+
if (typeof exp === "object" &&
|
|
381
|
+
exp !== null &&
|
|
382
|
+
((_a = exp.constructor) === null || _a === void 0 ? void 0 : _a.name) === "HttpError") {
|
|
383
|
+
const value = exp.status;
|
|
384
|
+
if (typeof value === "number")
|
|
385
|
+
actual = value;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
catch (_b) {
|
|
389
|
+
// A thrown value with unreadable properties cannot establish an
|
|
390
|
+
// HTTP status; it fails the same expectation as any other value.
|
|
391
|
+
}
|
|
392
|
+
return actual !== undefined && status.some((val) => val === actual)
|
|
393
|
+
? null
|
|
394
|
+
: new Error(message(actual));
|
|
395
|
+
};
|
|
280
396
|
try {
|
|
281
397
|
const output = task();
|
|
282
398
|
if (is_promise(output))
|
|
283
|
-
return
|
|
284
|
-
|
|
399
|
+
return output.then(() => {
|
|
400
|
+
throw new Error(message());
|
|
401
|
+
}, (exp) => {
|
|
285
402
|
const res = predicate(exp);
|
|
286
403
|
if (res)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
resolve();
|
|
290
|
-
})
|
|
291
|
-
.then(() => reject(new Error(message()))));
|
|
404
|
+
throw res;
|
|
405
|
+
});
|
|
292
406
|
else
|
|
293
407
|
throw new Error(message());
|
|
294
408
|
}
|
|
@@ -308,35 +422,70 @@ var TestValidator;
|
|
|
308
422
|
* sequence. Commonly used for testing database queries, search results, and
|
|
309
423
|
* any paginated data APIs.
|
|
310
424
|
*
|
|
425
|
+
* A page holds the first entities, so only the common prefix of the two lists
|
|
426
|
+
* is compared. An empty result against expected entities, or the reverse,
|
|
427
|
+
* fails.
|
|
428
|
+
*
|
|
429
|
+
* Processing cost: Only the common prefix of P entities is sliced, converted
|
|
430
|
+
* to id strings and compared, giving O(P) entries and temporary storage;
|
|
431
|
+
* string conversion and comparison add their text costs.
|
|
432
|
+
*
|
|
311
433
|
* @example
|
|
312
|
-
* ```typescript
|
|
313
434
|
* // Test article pagination
|
|
314
|
-
* const expectedArticles = await db.articles.findAll({
|
|
315
|
-
*
|
|
435
|
+
* const expectedArticles = await db.articles.findAll({
|
|
436
|
+
* order: "created_at DESC",
|
|
437
|
+
* });
|
|
438
|
+
* const actualArticles = await api.functional.getArticles({
|
|
439
|
+
* page: 1,
|
|
440
|
+
* limit: 10,
|
|
441
|
+
* });
|
|
316
442
|
*
|
|
317
|
-
* TestValidator.index(
|
|
318
|
-
*
|
|
443
|
+
* TestValidator.index(
|
|
444
|
+
* "article pagination order",
|
|
445
|
+
* expectedArticles,
|
|
446
|
+
* actualArticles,
|
|
447
|
+
* true, // enable trace logging
|
|
319
448
|
* );
|
|
320
449
|
*
|
|
321
450
|
* // Test user search results
|
|
322
|
-
* const manuallyFilteredUsers = allUsers.filter(u =>
|
|
323
|
-
*
|
|
451
|
+
* const manuallyFilteredUsers = allUsers.filter((u) =>
|
|
452
|
+
* u.name.includes("John"),
|
|
453
|
+
* );
|
|
454
|
+
* const apiSearchResults = await api.functional.searchUsers({
|
|
455
|
+
* query: "John",
|
|
456
|
+
* });
|
|
324
457
|
*
|
|
325
|
-
* TestValidator.index(
|
|
326
|
-
*
|
|
458
|
+
* TestValidator.index(
|
|
459
|
+
* "user search results",
|
|
460
|
+
* manuallyFilteredUsers,
|
|
461
|
+
* apiSearchResults,
|
|
462
|
+
* );
|
|
327
463
|
*
|
|
328
464
|
* @param title - Descriptive title used in error messages when order differs
|
|
329
465
|
* @param expected - The expected entities in correct order
|
|
330
466
|
* @param gotten - The actual entities returned by the API
|
|
331
467
|
* @param trace - Optional flag to enable debug logging (default: false)
|
|
332
468
|
* @throws Error when entity order differs between expected and actual results
|
|
469
|
+
* over their common prefix, or when exactly one of them is empty
|
|
470
|
+
* @evidence contracts/common.md#principled-implementation A page holds the first entities of the expected list, so the two id lists are compared over their common prefix, and one side being empty while the other is not is a difference because there is no prefix to compare; ids are compared as strings.
|
|
471
|
+
* @evidence contracts/common.md#clear-and-simple-design One function over the id lists produced by a private helper, with an optional trace flag.
|
|
472
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts Only ids are compared, with no entity type or field named in the function.
|
|
473
|
+
* @evidence contracts/common.md#meaningful-documentation The comment states the prefix rule, the empty rule, the trace flag, and the thrown error, with examples.
|
|
333
474
|
*/
|
|
334
475
|
TestValidator.index = (title, expected, gotten, trace = false) => {
|
|
476
|
+
// a page holds the first entities, so only the common prefix is compared,
|
|
477
|
+
// but an empty side against a non-empty one has no prefix to prove
|
|
478
|
+
if ((expected.length === 0) !== (gotten.length === 0)) {
|
|
479
|
+
if (trace === true)
|
|
480
|
+
console.log({
|
|
481
|
+
expected: get_ids(expected),
|
|
482
|
+
gotten: get_ids(gotten),
|
|
483
|
+
});
|
|
484
|
+
throw new Error(`Bug on ${title}: result of the index is different with manual aggregation.`);
|
|
485
|
+
}
|
|
335
486
|
const length = Math.min(expected.length, gotten.length);
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const xIds = get_ids(expected).slice(0, length);
|
|
339
|
-
const yIds = get_ids(gotten).slice(0, length);
|
|
487
|
+
const xIds = get_ids(expected.slice(0, length));
|
|
488
|
+
const yIds = get_ids(gotten.slice(0, length));
|
|
340
489
|
const equals = xIds.every((x, i) => x === yIds[i]);
|
|
341
490
|
if (equals === true)
|
|
342
491
|
return;
|
|
@@ -356,15 +505,27 @@ var TestValidator;
|
|
|
356
505
|
* API, and compares results. Validates that search APIs return the correct
|
|
357
506
|
* subset of data matching the search criteria.
|
|
358
507
|
*
|
|
508
|
+
* Processing cost: For K sampled entities from N entries, sparse sampling
|
|
509
|
+
* costs O(K), each reference filter scans N entries and each returned page
|
|
510
|
+
* prefix is compared once. Calls are sequential and one sample result is live
|
|
511
|
+
* at a time.
|
|
512
|
+
*
|
|
513
|
+
* Reuse: Each invocation must evaluate its supplied task or request to
|
|
514
|
+
* observe current behavior; prior results cannot replace these effectful
|
|
515
|
+
* checks.
|
|
516
|
+
*
|
|
517
|
+
* Resource ownership: The returned promise retains invocation-local state
|
|
518
|
+
* until settlement. The callback owns its external resources, and this
|
|
519
|
+
* validator provides no cancellation or timeout.
|
|
520
|
+
*
|
|
359
521
|
* @example
|
|
360
|
-
* ```typescript
|
|
361
522
|
* // Test article search functionality with exact matching
|
|
362
523
|
* const allArticles = await db.articles.findAll();
|
|
363
524
|
* const searchValidator = TestValidator.search(
|
|
364
525
|
* "article search API",
|
|
365
526
|
* (req) => api.searchArticles(req),
|
|
366
527
|
* allArticles,
|
|
367
|
-
* 5 // test with 5 random samples
|
|
528
|
+
* 5, // test with 5 random samples
|
|
368
529
|
* );
|
|
369
530
|
*
|
|
370
531
|
* // Test exact match search
|
|
@@ -372,7 +533,7 @@ var TestValidator;
|
|
|
372
533
|
* fields: ["title"],
|
|
373
534
|
* values: (article) => [article.title], // full title for exact match
|
|
374
535
|
* filter: (article, [title]) => article.title === title, // exact match
|
|
375
|
-
* request: ([title]) => ({ search: { title } })
|
|
536
|
+
* request: ([title]) => ({ search: { title } }),
|
|
376
537
|
* });
|
|
377
538
|
*
|
|
378
539
|
* // Test partial match search with includes
|
|
@@ -380,7 +541,7 @@ var TestValidator;
|
|
|
380
541
|
* fields: ["content"],
|
|
381
542
|
* values: (article) => [article.content.substring(0, 20)], // partial content
|
|
382
543
|
* filter: (article, [keyword]) => article.content.includes(keyword),
|
|
383
|
-
* request: ([keyword]) => ({ q: keyword })
|
|
544
|
+
* request: ([keyword]) => ({ q: keyword }),
|
|
384
545
|
* });
|
|
385
546
|
*
|
|
386
547
|
* // Test multi-field search with exact matching
|
|
@@ -389,9 +550,8 @@ var TestValidator;
|
|
|
389
550
|
* values: (article) => [article.writer, article.title],
|
|
390
551
|
* filter: (article, [writer, title]) =>
|
|
391
552
|
* article.writer === writer && article.title === title,
|
|
392
|
-
* request: ([writer, title]) => ({ search: { writer, title } })
|
|
553
|
+
* request: ([writer, title]) => ({ search: { writer, title } }),
|
|
393
554
|
* });
|
|
394
|
-
* ```;
|
|
395
555
|
*
|
|
396
556
|
* @param title - Descriptive title used in error messages when search fails
|
|
397
557
|
* @param getter - API function that performs the search
|
|
@@ -399,6 +559,10 @@ var TestValidator;
|
|
|
399
559
|
* @param sampleCount - Number of random samples to test (default: 1)
|
|
400
560
|
* @returns A function that accepts search configuration properties
|
|
401
561
|
* @throws Error when API search results don't match manual filtering results
|
|
562
|
+
* @evidence contracts/common.md#principled-implementation A sample of entities is drawn, the search values are read from each, the expected set is computed by a manual filter over the whole dataset, the API is called with the request built from the values, and `index` compares the two lists, so the API is checked against a filter the test author wrote independently.
|
|
563
|
+
* @evidence contracts/common.md#clear-and-simple-design The function is curried so the dataset and the API are given once and each search configuration is run against them, and the comparison is delegated to `index`.
|
|
564
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts The sample, values, filter, and request all come from the caller's configuration.
|
|
565
|
+
* @evidence contracts/common.md#meaningful-documentation The comment documents the steps, the parameters, and the thrown error, with examples.
|
|
402
566
|
*/
|
|
403
567
|
TestValidator.search = (title, getter, total, sampleCount = 1) => (props) => __awaiter(this, void 0, void 0, function* () {
|
|
404
568
|
const samples = RandomGenerator_1.RandomGenerator.sample(total, sampleCount);
|
|
@@ -417,15 +581,24 @@ var TestValidator;
|
|
|
417
581
|
* ascending/descending order, and optional filtering. Provides detailed error
|
|
418
582
|
* reporting for sorting failures.
|
|
419
583
|
*
|
|
584
|
+
* Processing cost: After one getter call, an optional filter scans N results
|
|
585
|
+
* and sortedness checks at most N minus one adjacent pairs. Signed field
|
|
586
|
+
* construction costs O(F); filter output costs O(N) space when requested.
|
|
587
|
+
*
|
|
588
|
+
* Reuse: Each invocation must evaluate its supplied task or request to
|
|
589
|
+
* observe current behavior; prior results cannot replace these effectful
|
|
590
|
+
* checks.
|
|
591
|
+
*
|
|
592
|
+
* Resource ownership: The returned promise retains invocation-local state
|
|
593
|
+
* until settlement. The callback owns its external resources, and this
|
|
594
|
+
* validator provides no cancellation or timeout.
|
|
595
|
+
*
|
|
420
596
|
* @example
|
|
421
|
-
* ```typescript
|
|
422
597
|
* // Test single field sorting with GaffComparator
|
|
423
598
|
* const sortValidator = TestValidator.sort(
|
|
424
599
|
* "article sorting",
|
|
425
|
-
* (sortable) => api.getArticles({ sort: sortable })
|
|
426
|
-
* )("created_at")(
|
|
427
|
-
* GaffComparator.dates((a) => a.created_at)
|
|
428
|
-
* );
|
|
600
|
+
* (sortable) => api.getArticles({ sort: sortable }),
|
|
601
|
+
* )("created_at")(GaffComparator.dates((a) => a.created_at));
|
|
429
602
|
*
|
|
430
603
|
* await sortValidator("+"); // ascending
|
|
431
604
|
* await sortValidator("-"); // descending
|
|
@@ -433,10 +606,10 @@ var TestValidator;
|
|
|
433
606
|
* // Test multi-field sorting with GaffComparator
|
|
434
607
|
* const userSortValidator = TestValidator.sort(
|
|
435
608
|
* "user sorting",
|
|
436
|
-
* (sortable) => api.getUsers({ sort: sortable })
|
|
609
|
+
* (sortable) => api.getUsers({ sort: sortable }),
|
|
437
610
|
* )("lastName", "firstName")(
|
|
438
611
|
* GaffComparator.strings((user) => [user.lastName, user.firstName]),
|
|
439
|
-
* (user) => user.isActive // only test active users
|
|
612
|
+
* (user) => user.isActive, // only test active users
|
|
440
613
|
* );
|
|
441
614
|
*
|
|
442
615
|
* await userSortValidator("+", true); // ascending with trace logging
|
|
@@ -444,20 +617,24 @@ var TestValidator;
|
|
|
444
617
|
* // Custom comparator for complex logic
|
|
445
618
|
* const customSortValidator = TestValidator.sort(
|
|
446
619
|
* "custom sorting",
|
|
447
|
-
* (sortable) => api.getProducts({ sort: sortable })
|
|
448
|
-
* )(
|
|
449
|
-
*
|
|
450
|
-
*
|
|
451
|
-
*
|
|
452
|
-
*
|
|
453
|
-
*
|
|
454
|
-
*
|
|
620
|
+
* (sortable) => api.getProducts({ sort: sortable }),
|
|
621
|
+
* )(
|
|
622
|
+
* "price",
|
|
623
|
+
* "rating",
|
|
624
|
+
* )((a, b) => {
|
|
625
|
+
* const priceDiff = a.price - b.price;
|
|
626
|
+
* return priceDiff !== 0 ? priceDiff : b.rating - a.rating; // price asc, rating desc
|
|
627
|
+
* });
|
|
455
628
|
*
|
|
456
629
|
* @param title - Descriptive title used in error messages when sorting fails
|
|
457
630
|
* @param getter - API function that fetches sorted data
|
|
458
631
|
* @returns A currying function chain: field names, comparator, then direction
|
|
459
632
|
* @throws Error when API results are not properly sorted according to
|
|
460
633
|
* specification
|
|
634
|
+
* @evidence contracts/common.md#principled-implementation The API is called with the signed field names, its result is optionally filtered, and the comparator (reversed for the descending direction) is applied to adjacent pairs, so any adjacent pair out of order fails; checking adjacent pairs suffices for a total preorder.
|
|
635
|
+
* @evidence contracts/common.md#clear-and-simple-design The function is curried in three steps so the request, the comparator, and the direction can be given at the moments each is known.
|
|
636
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts The order is judged by the caller's comparator, with no field or direction special-cased.
|
|
637
|
+
* @evidence contracts/common.md#meaningful-documentation The comment documents each stage, the parameters, and the thrown error, with examples.
|
|
461
638
|
*/
|
|
462
639
|
TestValidator.sort = (title, getter) => (...fields) => (comp, filter) => (direction_1, ...args_1) => __awaiter(this, [direction_1, ...args_1], void 0, function* (direction, trace = false) {
|
|
463
640
|
let data = yield getter(fields.map((field) => `${direction}${field}`));
|